Delphi Change resolution of webcam in directshow - delphi

i am trying to change the resolution of the frames to 320x240 because my webcam is providing frames in 640x480 and the encoder i am using is not working right with higher resolution, i do it this way
procedure OnDevieStart()
begin
FilterGraph.ClearGraph;
FilterGraph.Active := False;
Filter.BaseFilter.Moniker := SysDev.GetMoniker(TMenuItem(Sender).tag);
FilterGraph.Active := true;
SetVideoProperties(Filter as iBaseFilter);
with FilterGraph as ICaptureGraphBuilder2 do
try
RenderStream(#PIN_CATEGORY_PREVIEW, nil, Filter as IBaseFilter, SampleGrabber as IBaseFilter, VideoWindow as IbaseFilter);
FilterGraph.Play;
except
ShowMessage('Unable to use specified device!')
end;
end;
function SetVideoProperties(pVideoCapture: IBaseFilter):Boolean;
var
hr:HRESULT;
pStreamConfig: IAMStreamConfig;
pAM_Media: PAMMediaType;
pvih: PVIDEOINFOHEADER;
pICGP2: ICaptureGraphBuilder2;
begin
pICGP2 := FilterGraph as ICaptureGraphBuilder2;
hr := pICGP2.FindInterface(#PIN_CATEGORY_CAPTURE, nil, pVideoCapture,
IID_IAMStreamConfig, pStreamConfig);
if (SUCCEEDED(hr)) then begin
pStreamConfig.GetFormat(pAM_Media);
pvih := pAM_Media.pbFormat ;
pAM_Media.subtype := MEDIASUBTYPE_RGB24;
pvih.bmiHeader.biWidth := 320;
pvih.bmiHeader.biHeight := 240;
pvih.AvgTimePerFrame := 10000000 div 15;
pStreamConfig.SetFormat(pAM_Media^);
DeleteMediaType(pAM_Media);
pStreamConfig := nil;
end;
end;
But the resolution stays the same when grabbing the frames through the sample grabber
Is there anything wrong with this approach?
UPDATE
Ok i think i am now updating all the members
function SetVideoProperties(pVideoCapture: IBaseFilter):Boolean;
var
hr:HRESULT;
pStreamConfig: IAMStreamConfig;
pAM_Media: PAMMediaType;
pvih: PVIDEOINFOHEADER;
pICGP2: ICaptureGraphBuilder2;
begin
pICGP2 := FilterGraph as ICaptureGraphBuilder2;
hr := pICGP2.FindInterface(#PIN_CATEGORY_CAPTURE, nil, pVideoCapture,
IID_IAMStreamConfig, pStreamConfig);
if (SUCCEEDED(hr)) then begin
pStreamConfig.GetFormat(pAM_Media);
pAM_Media.subtype := MEDIASUBTYPE_RGB24;
pAM_Media.majortype := MEDIATYPE_Video;
pAM_Media.bFixedSizeSamples := True;
pAM_Media.bTemporalCompression := False;
pAM_Media.lSampleSize := 230400;
pAM_Media.formattype := FORMAT_VideoInfo;
pAM_Media.pUnk := nil;
pAM_Media.cbFormat := 88;
pvih := pAM_Media.pbFormat;
pvih.dwBitRate := 6912000;
pvih.AvgTimePerFrame := 10000000 div 15;
pvih.bmiHeader.biSize := 40;
pvih.bmiHeader.biWidth := 320;
pvih.bmiHeader.biHeight := 240;
pvih.bmiHeader.biPlanes := 1;
pvih.bmiHeader.biBitCount := 24;
pvih.bmiHeader.biCompression := 0;
pvih.bmiHeader.biSizeImage := 230400;
pvih.bmiHeader.biXPelsPerMeter := 0;
pvih.bmiHeader.biYPelsPerMeter := 0;
pvih.bmiHeader.biClrUsed := 0;
pvih.bmiHeader.biClrImportant := 0;
hr := pStreamConfig.SetFormat(pAM_Media^);
If Succeeded(hr) then ShowMessage('SUCCEED') else ShowMessage(IntToStr(hr));
DeleteMediaType(pAM_Media);
pStreamConfig := nil;
end;
end;

Your initialization of new media type with new resolution is incorrect/incomplete: update other members as well
You should be checking SetFormat result to detect failures in format settings
The code itself appears to be incomplete, there is no evidence you are at all changing the format and the source filter exists and added to the filter graph

See DSPack demo "...\dspack2.3.4\Demos\D6-D7\videocap"
You need to enumerate all availible formats of webcam, and then set one.
Thats the code from there:
(button Start OnClick handler)
VideoMediaTypes,
AudioMediaTypes: TEnumMediaType;
.......
// configure output Video media type
if VideoSourceFilter.FilterGraph <> nil then
begin
PinList := TPinList.Create(VideoSourceFilter as IBaseFilter);
if VideoFormats.ItemIndex <> -1 then
with (PinList.First as IAMStreamConfig) do
SetFormat(VideoMediaTypes.Items[VideoFormats.ItemIndex].AMMediaType^);
PinList.Free;
end;
Here
SetFormat(VideoMediaTypes.Items[VideoFormats.ItemIndex].AMMediaType^);
VideoMediaTypes is a list of available formats that populated when user selects the Source.
VideoFormat is GUI control (ListBox) for selecting the format

Related

DirectX/DirectCompute CreateBuffer failure with error 0x80070057 (E_INVALIDARG)

I'm trying to create a buffer in GPU memory to upload data from CPU. GPU access will be readonly. Data will be used as an input buffer for a compute shader.
CreateBuffer() fails with error 0x80070057 (E_INVALIDARG). I read the docs and read it again without discovering which argument cause the failure.
InitDevice() return success.
Here is an extract from my code:
function TGpuImageControl.InitDevice: HRESULT;
var
hr : HRESULT;
createDeviceFlags : UINT;
driverTypes : array [0..0] of D3D_DRIVER_TYPE;
numDriverTypes : UINT;
driverTypeIndex : UINT;
sd : DXGI_SWAP_CHAIN_DESC;
FeatureLevels : D3D_FEATURE_LEVEL;
featureLevel : D3D_FEATURE_LEVEL;
const
D3D10_SHADER_DEBUG = 1;
begin
hr := S_OK;
createDeviceFlags := 0;
{$ifdef DEBUG}
createDeviceFlags := createDeviceFlags or D3D11_CREATE_DEVICE_DEBUG;
{$endif}
{$ifdef WARP}
driverTypes[0] := D3D_DRIVER_TYPE_REFERENCE;
{$else}
driverTypes[0] := D3D_DRIVER_TYPE_HARDWARE;
{$endif}
numDriverTypes := SizeOf(driverTypes) div SizeOf(driverTypes[0]);
ZeroMemory(#sd, SizeOf(sd));
sd.BufferCount := 1;
sd.BufferDesc.Width := width;
sd.BufferDesc.Height := height;
sd.BufferDesc.Format := DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator := 60;
sd.BufferDesc.RefreshRate.Denominator := 1;
sd.BufferUsage := DXGI_USAGE_RENDER_TARGET_OUTPUT or
DXGI_USAGE_UNORDERED_ACCESS;// or
//DXGI_USAGE_SHADER_INPUT;
sd.OutputWindow := Handle;
sd.SampleDesc.Count := 1;
sd.SampleDesc.Quality := 0;
sd.Windowed := TRUE;
//sd.Flags := DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
FeatureLevels := D3D_FEATURE_LEVEL_11_0;
for driverTypeIndex := 0 to numDriverTypes do begin
g_driverType := driverTypes[driverTypeIndex];
hr := D3D11CreateDeviceAndSwapChain(
nil, // Graphic Adapter, use default
g_driverType, // Driver type to use
0, // HModule for software driver
createDeviceFlags, // Create flags
#FeatureLevels, // Feature levels
1, // Feature level size
D3D11_SDK_VERSION, // SDK Version
#sd, // Swap Chain descriptor
g_pSwapChain, // Out: Created swap chain
g_pd3dDevice, // Out: Created device
featureLevel, // Out: Feature level
g_pImmediateContext); // Out: Context
if SUCCEEDED(hr) then
break;
end;
if FAILED(hr) then begin
Result := hr;
Exit;
end;
ImageResize();
Result := S_OK;
end;
procedure TGpuImageControl.ImageResize;
var
hr : HRESULT;
sd : DXGI_SWAP_CHAIN_DESC;
pTexture : ID3D11Texture2D;
vp : D3D11_VIEWPORT;
begin
if g_pd3dDevice = nil then
Exit;
// release first else resize problem
SAFE_RELEASE(IUnknown(g_pComputeOutput));
g_pSwapChain.GetDesc(sd);
hr := g_pSwapChain.ResizeBuffers(sd.BufferCount,
Width,
Height,
sd.BufferDesc.Format,
0); // Swap chain flags
if FAILED(hr) then begin
ShowError('SwapChain.ResizeBuffers failed with error %d', [hr]);
Exit;
end;
hr := g_pSwapChain.GetBuffer(0, TGUID(ID3D11Texture2D), pTexture);
if FAILED(hr) then begin
ShowError('SwapChain.GetBuffer failed with error %d', [hr]);
Exit;
end;
// create shader unordered access view on back buffer for compute shader to write into texture
hr := g_pd3dDevice.CreateUnorderedAccessView(pTexture,
nil,
g_pComputeOutput);
if FAILED(hr) then begin
ShowError('pd3dDevice.CreateUnorderedAccessView failed with error %d', [hr]);
Exit;
end;
pTexture := nil;
// Setup the viewport
vp.Width := Width;
vp.Height := Height;
vp.MinDepth := 0.0;
vp.MaxDepth := 1.0;
vp.TopLeftX := 0;
vp.TopLeftY := 0;
g_pImmediateContext.RSSetViewports(1, #vp);
end;
The code which fails is the following:
function TGpuImageControl.CreateStructuredBuffer(
uElementSize : UINT;
uCount : UINT;
pInitData : Pointer;
out ppBufOut : ID3D11Buffer): HRESULT;
var
desc : D3D11_BUFFER_DESC;
InitData : D3D11_SUBRESOURCE_DATA;
begin
ppBufOut := nil;
ZeroMemory(#desc, SizeOf(desc));
desc.BindFlags := D3D11_BIND_UNORDERED_ACCESS or
D3D11_BIND_SHADER_RESOURCE;
desc.Usage := D3D11_USAGE_DYNAMIC;
desc.CPUAccessFlags := D3D11_CPU_ACCESS_WRITE;
desc.ByteWidth := uElementSize * uCount;
desc.MiscFlags := UINT(D3D11_RESOURCE_MISC_BUFFER_STRUCTURED);
desc.StructureByteStride := uElementSize;
if pInitData <> nil then begin
InitData.pSysMem := pInitData;
Result := g_pd3dDevice.CreateBuffer(desc, #InitData, ppBufOut);
end
else
Result := g_pd3dDevice.CreateBuffer(desc, nil, ppBufOut);
end;
When calling the function, I pass uElementSize=2, uCount=100 and pInitData pointing to an allocated 200 bytes buffer in CPU memory.
I don't understand what I'm doing wrong.
Any help appreciated.
The answer has been given by Chuck Walbourn to the C++ question I asked there DirectCompute CreateBuffer fails with error 0x80070057 (E_INVALIDARG)
The most important part to debug this error is simply look at Delphi Event Viewer and just look the error message the API is triggering when debugging is enabled (I I already had enabled debugging but didn't figured that messages where output to the events windows).

FASTREPORT Adding objects dynamically in report script

I try to add TLineView objects to a report.
The number of lines is depending on a certain number, retrieved by the reports dataset.
I have put my code into the scripts initialization part and in a very experimental test version it looks like this:
var nol, i: integer;
child, newChild: TfrxChild;
noteLine1, noteLine2: TfrxLineView;
page: TfrxPage;
begin
page := ReportName;
nol := <DS_MAIN."VOLUME"> /2;
nol := nol + <DS_MAIN."VOLUME"> mod 2;
child3.child := TfrxChild.create(nil);
newchild := child3.child;
newChild.Visible := true;
noteLine1 := TfrxLineView.create(newChild);
noteLine1.name := 'nl1000';
noteLine1.Top := 0.73;
noteLine1.Width := 7.5;
noteLine1.Left := 3;
noteLine1.Visible := true;
noteLine1.Parent.Objects.Remove(noteLine1);
noteLine1.Parent.Objects.Add(noteLine1);
// newChild.Objects.Add(noteLine1);
noteLine2 := TfrxLineView.create(newChild);
noteLine2.name := 'nl1001';
noteLine2.Top := 0.73;
noteLine2.Width := 7.5;
noteLine2.Left := 11.2;
newChild.Objects.Add(noteLine2);
noteLine2.Visible := true;
for i := 1 to nol do begin
Child := TfrxChild.create(nil);
NewChild.child := Child;
newChild := child;
end;
end.
Instead of getting two lines side by side, with a gap between them, I get only a single short line of a length of around 3-4 mm.
The above code is just a snap of my trial-and-error session.
Hope now that there could be anyone to give me some clues.
If I understand your question correctly, you need to consider at least the following:
With your for loop you create bands, not lines. You may try to change the logic and create objects (memos, lines, shapes) with bands as owners.
The objects’ coordinates and sizes are set in pixels, so you need an additional calculation.
From documentation:
Objects’ coordinates and sizes are set in pixels. Since the «Left,»
«Top,» «Width,» and «Height» properties of all objects have the
«Extended» type, you can point out non-integer values. The following
constants are defined for converting pixels into centimeters and
inches:
fr01cm = 3.77953;
fr1cm = 37.7953;
fr01in = 9.6;
fr1in = 96;
The following working example generates five TfrxLineView objects. Just put an empty report on your form and add report title band:
procedure TfrmMain.btnPreviewClick(Sender: TObject);
var
nol, i: integer;
left: Extended;
band: TfrxReportTitle;
line: TfrxLineView;
begin
// Band
band := (report.Report.FindObject('ReportBand') as TfrxReportTitle);
// Lines generation
left := 3;
nol := 5;
for i := 1 to nol do begin
line := TfrxLineView.Create(band);
line.CreateUniqueName;
line.Top := 0.73;
line.Width := fr1cm * 2;
line.Left := left;
left := left + line.Width + 30;
end;
// Report preview
report.ShowReport(False);
end;
This is my final solution:
procedure Child8OnBeforePrint(Sender: TfrxComponent);
var nol, i: integer;
left1, left2: extended;
child, newChild: TfrxChild;
noteLine1, noteLine2, line: TfrxLineView;
page: TfrxPage;
band: TfrxChild;
begin
nol := <DS_MAIN."VOLUME"> /2;
nol := nol + <DS_MAIN."VOLUME"> mod 2;
band := TfrxChild(TRP_ORDER_NOTE.FindObject('Child9'));
// Lines generation
left1 := 3*fr1cm;
left2 := 11.2*fr1cm;
for i := 1 to nol do begin
line := TfrxLineView.Create(band);
line.Name := 'noteLine'+intToStr(1+2*(i-1+trunc(random*1000000))); //Panic solution
line.Top := fr1cm*(0.73 + (i-1)*0.75);
line.Width := 7.5*fr1cm;
line.Left := left1;
if (<DS_MAIN."VOLUME"> mod 2 > 0 ) and (i = nol) then
exit
else
begin
line := TfrxLineView.Create(band);
line.Name := 'noteLine'+intToStr(2*i+trunc(random*1000000));
line.Top := fr1cm*(0.73 + (i-1)*0.75);
line.Width := 7.5*fr1cm;
line.Left := left2;
end;
end;
end;

Lag when Scrolling a TListBox

I have a TListBox that contains about 50 TListboxItems aka Items. Each item contains 3 TTexts used as labels, 1 TImage with resolution of 48x48 to indicate a 'status', and a Check box for selecting items. When on a device, there is a big lag time when scrolling. It is often jumpy,sporadic,inconsistent.
Is this because I have too many items ? Or is it because they contain the TTexts,Timage, etc. ?
Or is there something I can do to smooth up the scrolling process of the TListbox.
I am using Delphi xe5 to develop an iOS application. I did make sure to check that the 'sorted' property of the TListbox is := False;
UPDATE (Response to Jerry Dodge):
while XMLNode <> nil do begin
Main_Form.LBoxEntries.Items.Add('');
Item1:=Main_Form.LBoxEntries.ListItems[Main_Form.LBoxEntries.Items.Count-1];
Item1.Height := 80;
Item1.Width := ClientWidth;
if XMLNode.ChildNodes['SCANSTATUS'].Text = '0' then begin
Item1.ItemData.Bitmap := Main_Form.Red.Bitmap;
Item1.Tag := 0;
end;
if XMLNode.ChildNodes['SCANSTATUS'].Text = '1' then begin
Item1.ItemData.Bitmap := Main_Form.Orange.Bitmap;
Item1.Tag := 1;
end;
if XMLNode.ChildNodes['SCANSTATUS'].Text = '2' then begin
Item1.ItemData.Bitmap := Main_Form.Green.Bitmap;
Item1.Tag := 2;
end;
Customer := TText.Create(nil);
Customer.Parent := Item1;
Customer.Position.X := 95;
Customer.Position.Y := 8;
Customer.Text := XMLNode.childNodes['CUSTOMERNAME'].text;
Customer.Width := Item1.Width - 105;
Customer.WordWrap := False;
Customer.Color := TAlphaColors.Blue;
Customer.Trimming := TTextTrimming(1);
Customer.Height := 20;
Customer.Font.Size := 18;
Customer.HorzTextAlign := TTextAlign(1);
Customer.Anchors := [TanchorKind.akLeft,TanchorKind.akRight];
Customer.WordWrap := False;
Product := TText.Create(nil);
Product.Parent := Item1;
Product.Position.X := 105;
Product.Position.Y := 30;
Product.Text := 'Product: ' +XMLNode.childNodes['PRODUCT'].text;
Product.Width := Item1.Width - 115;
Product.Trimming := TTextTrimming(1);
Product.Height := 20;
Product.Font.Size := 15;
Product.HorzTextAlign := TTextAlign(1);
Product.Anchors := [TanchorKind.akLeft,TanchorKind.akRight];
Product.WordWrap := False;
QTY := TText.Create(nil);
QTY.Parent := Item1;
QTY.Position.X := 105;
QTY.Position.Y := 50;
QTY.Text := 'QTY: ('+XMLNode.childNodes['QTY'].text+')';
QTY.Width := Item1.Width - 115;
QTY.Trimming := TTextTrimming(1);
QTY.Height := 20;
QTY.Font.Size := 15;
QTY.HorzTextAlign := TTextAlign(1);
QTY.Anchors := [TanchorKind.akLeft,TanchorKind.akRight];
QTY.WordWrap := False;
Item1.ItemData.Detail := ' |' + XMLNode.childNodes['SID'].Text+'|'+
' |' + XMLNode.childNodes['CUSTOMERNAME'].Text+'|'+
' |' + XMLNode.childNodes['PRODUCT'].text+'|'+
' |' + XMLNode.childNodes['QTY'].Text+'| ';
XMLNode := XMLNode.NextSibling;
end;
Main_Form.LBoxEntries.EndUpdate;
No post actions/events are tied to the items.
I removed all of the TLayouts I was using, of which my Listbox was placed upon - still lagged.
I then removed the parent TPanel that acted as the form control (for sliding effect when opening a side menu), and then the lag disappeared. I will do further testing to see if I can just swap the TPanel with a TLayout, or just adjust my program and side-menu accordingly.
Update: TPanel is what caused the lagg when scrolling. Swapped the component for a TLayout and it works smoothly as ever !
I think the standard advice is if it needs to scroll, use a TListView, not a TListbox. I have done simple apps on iOS and Android with XE5 with 100+ items in a TListView and scrolling has been very smooth.

Trouble with dynamic array of timage and tlabel in delphi

I want to add an attachment, and have the form grow longer each time an attachment is added, to make room for a line that holds information about the attachment with a label and some 16X16 images. For this I chose to use a dynamic array (not sure whether that's best). each time an attachment is added, I want to create a new instance of these objects. My code doesn't seem to work. what's wrong with the follwing code?
procedure TVisionMail.AddAttachment(FileString: String);
var
I: Integer;
begin
AttCount := AttCount + 1; // increment attachment count
//set attachment file name
if (AttCount <> 0) and (edAttachment.Text <> '') then
edAttachment.text := edAttachment.text + ';';
edAttachment.text := edAttachment.text + FileString;
//move objects position down to allow space for attachment line
VisionMail.Height := VisionMail.Height + 25;
Panel1.Height := Panel1.Height + 25;
btnSend.Top := btnSend.Top + 25;
btnExit.Top := btnExit.Top + 25;
StatusMemo.Top := StatusMemo.Top + 25;
Memo1.Top := Memo1.Top + 25;
lblBody.Top := lblBody.Top + 25;
//Allocate memory for arrays
SetLength(newImg, AttCount);
SetLength(newlbl, AttCount);
SetLength(newDel, AttCount);
SetLength(newPin, AttCount);
//create new instance and set parents, positions, color, events
newImg[AttCount]:= TImage.Create(VisionMail);
with newImg[AttCount] do
begin
Parent := Panel1;
Top := Memo1.Top - 25;
Left := 408;
Height := 16;
Width := 16;
end;
newlbl[AttCount]:= TLabel.Create(VisionMail);
with newlbl[AttCount] do
begin
Parent := Panel1;
Top := newImg[I].Top + 2;
Left := 397;
Height := 3;
Width := 13;
BiDiMode := bdRightToLeft;
end;
newDel[AttCount] := TAdvToolButton.Create(VisionMail);
with newDel[AttCount] do
begin
Parent := Panel1;
Top := newImg[I].Top;
Left := 440;
Height := 16;
Width := 16;
color := clBtnFace;
colorChecked := clBtnFace;
colorDown := clBtnFace;
colorHot := clBtnFace;
OnClick := btnDelAttClick;
OnMouseEnter := btnDelAttMouseEnter;
OnMouseLeave := btnDelAttMouseLeave;
end;
newPin[AttCount] := TImage.Create(VisionMail);
with newDel[AttCount] do
begin
Parent := Panel1;
Top := newImg[I].Top;
Left := 425;
Height := 16;
Width := 16;
end;
//get Icon for extension of file
lstIcons.GetBitmap(GetIcon(ExtractFileExt
(OpenDialog1.FileName)),
newImg[AttCount].Picture.Bitmap);
newlbl[AttCount].Caption := ExtractFileName(FileString);
end;
The most obvious flaw is that you are writing off the end of all of your arrays. For example, you write
SetLength(newImg, AttCount);
and that means that the valid indices for newImg are 0 to AttCount-1 inclusive. But then you write
newImg[AttCount] := ...
and that is an out of bounds access because the last index is AttCount-1. You do the same for all your array access.
If you compile with range checking enabled, the compiler will generate a runtime error that explains what you have done wrong.
Personally I think you would be better using a record to hold your four components:
TAttachmentControls = record
Img: TImage;
Lbl: TLabel;
.. etc.
end;
And use a TList<TAttachmentControls> as your container.

TRichEdit color problems

ans:= RichEdit1.Text
for i:=1 to Length(ans) do
begin
RichEdit1.SelStart := i-1;
RichEdit1.SelLength:= 1;
if ans[i] = correct[i] then
RichEdit1.SelAttributes.Color := clRed
else
RichEdit1.SelAttributes.Color := clBlue;
If the letter in ans matches the letter in the same position as the letter in correct string, it is colored red otherwise, it is blue.
My problem is when I type again the whole RichEdit1 text is colored as the same as the first letter (if the first letter of RichEdit1 is blue then the whole text becomes blue).
By the way, this is not the the actual code I just simplified it because there are mutiple TRichEdits.
The TRichEdits are read-only and I assign the letters by something like RichEdit1.Text := RichEdit1.Text+Key;
(doing this because it's a multiple keyboard program and I need to separate user inputs)
Is this the correct behavior? How can I stop my color changes from overriding the default color?
update: Solved it... in a sloppy way (applying the default color whenever someone types), but I'm keeping this open in case someone comes up with a better solution.
As you already discovered, you have to reset the default color when you are done, eg:
ans := RichEdit1.Text;
for i := 1 to Length(ans) do
begin
RichEdit1.SelStart := i-1;
RichEdit1.SelLength := 1;
if ans[i] = correct[i] then
RichEdit1.SelAttributes.Color := clRed
else
RichEdit1.SelAttributes.Color := clBlue;
end;
RichEdit1.SelStart := RichEdit1.GetTextLen;
RichEdit1.SelLength := 0;
RichEdit1.SelAttributes.Color := RichEdit1.Font.Color;
There are more efficient ways to handle this than coloring one letter at a time, eg:
const
colors: array[Boolean] of TColor = (clRed, clBlue);
var
ans: string;
start, len: Integer;
cur_state: Boolean;
procedure ColorRange(AStart, ALength: Integer; AColor: TColor);
begin
RichEdit1.SelStart := AStart;
RichEdit1.SelLength := ALength;
RichEdit1.SelAttributes.Color := AColor;
end;
begin
RichEdit1.Lines.BeginUpdate;
try
ans := RichEdit1.Text;
start := 0;
len := 0;
cur_start := False;
for i := 1 to Length(ans) do
begin
if (ans[i] = correct[i]) = cur_state then
Inc(len)
else begin
if len > 0 then
ColorRange(start, len, colors[cur_state]);
start := i-1;
len := 1;
cur_state := not cur_state;
end;
end;
if len > 0 then
ColorRange(start, len, colors[cur_state]);
ColorRange(RichEdit1.GetTextLen, 0, RichEdit1.Font.Color);
finally
RichEdit1.Lines.EndUpdate;
end;
end;
Also, using the Text property to append a single Char is very inefficient. Use the SelText property instead, eg:
RichEdit1.SelStart := RichEdit1.GetTextLen;
RichEdit1.SelLength := 0;
RichEdit1.SelAttributes.Color := ...; // optional
RichEdit1.SelText := Key;

Resources