-
Постов
61 -
Зарегистрирован
-
Посещение
-
Победитель дней
6
Сообщения, опубликованные IVGSoft
-
-
19 часов назад, Roma77751 сказал:
так компилируется, но все все равно синее
Не помню, чтобы с таким сталкивался. Возможно где-то ошибка в исходниках FMX. Похоже на то, что перепутаны местами каналы R и B
-
В 23.11.2018 в 21:28, Roma77751 сказал:
Доброго времени суток! в твоем коде делфя ругается на эту строчку
" MergeBitmap(aFrames[Index].Bitmap, aDisplay, aFrames[Index].Bitmap.Bounds, aFrames[Index].FPos.X, aFrames[Index].FPos.Y);"
а конкретно на" aFrames[Index].Bitmap.Bounds"
подскажи что не так плз...чет не могу разобраться
Конкретней можно? Какую ошибку выдает? Какая версия Дельфи?
-
-
Есть подозрение, что превышается максимальный размер TBitmap для данной платформы
-
В 27.06.2018 в 09:56, krapotkin сказал:
соответственно, какой уж там datasnap
Почему? Tokyo вроде позволяет создать и под Линукс.
-
-
17 минут назад, ENERGY сказал:
Я отправил автору коммент с этим кодом. Ничего он не забил, последний коммент в блоге был в недавно.
Хорошо. Пусть исправит, думаю это многим пригодится.
А куда отправили? Сюда - http://www.raysoftware.cn/?p=559
-
12 часов назад, Вадим Смоленский сказал:
Хорошо, если получится исправить. В моем-то проекте гифки самые простые, без прозрачности и черезстрочности, так что я ничего такого и не заметил.
В конце концов получилось исправить.
Для interlaced гифок проблема была вообще пустяковая. Одна строчка не в том месте. А вот для optimized все оказалось несколько сложней, но в итоге теперь все гифки отображаются правильно.
Пользуйтесь на здоровье!
-
Может и так, но я уже поборол все эти проблемы. Наконец. Вот, держите!
Испробовано на многих гифках.
Спойлерunit FMX.GifUtils; interface uses System.Classes, System.SysUtils, System.Types, System.UITypes, FMX.Types, FMX.Objects, FMX.Graphics, System.Generics.Collections; const alphaTransparent = $00; GifSignature: array [0 .. 2] of Byte = ($47, $49, $46); // GIF VerSignature87a: array [0 .. 2] of Byte = ($38, $37, $61); // 87a VerSignature89a: array [0 .. 2] of Byte = ($38, $39, $61); // 89a GIF_DISPOSAL_UNSPECIFIED = 0; GIF_DISPOSAL_LEAVE = 1; GIF_DISPOSAL_BACKGROUND = 2; GIF_DISPOSAL_PREVIOUS = 3; type TGifVer = (verUnknow, ver87a, ver89a); TInternalColor = packed record case Integer of 0: ( {$IFDEF BIGENDIAN} R, G, B, A: Byte; {$ELSE} B, G, R, A: Byte; {$ENDIF} ); 1: (Color: TAlphaColor; ); end; {$POINTERMATH ON} PInternalColor = ^TInternalColor; {$POINTERMATH OFF} TGifRGB = packed record R: Byte; G: Byte; B: Byte; end; TGIFHeader = packed record Signature: array [0 .. 2] of Byte; // * Header Signature (always "GIF") */ Version: array [0 .. 2] of Byte; // * GIF format version("87a" or "89a") */ // Logical Screen Descriptor ScreenWidth: word; // * Width of Display Screen in Pixels */ ScreenHeight: word; // * Height of Display Screen in Pixels */ Packedbit: Byte; // * Screen and Color Map Information */ BackgroundColor: Byte; // * Background Color Index */ AspectRatio: Byte; // * Pixel Aspect Ratio */ end; TGifImageDescriptor = packed record Left: word; // * X position of image on the display */ Top: word; // * Y position of image on the display */ Width: word; // * Width of the image in pixels */ Height: word; // * Height of the image in pixels */ Packedbit: Byte; // * Image and Color Table Data Information */ end; TGifGraphicsControlExtension = packed record BlockSize: Byte; // * Size of remaining fields (always 04h) */ Packedbit: Byte; // * Method of graphics disposal to use */ DelayTime: word; // * Hundredths of seconds to wait */ ColorIndex: Byte; // * Transparent Color Index */ Terminator: Byte; // * Block Terminator (always 0) */ end; TGifReader = class; TPalette = TArray<TInternalColor>; TGifFrameItem = class; TGifFrameList = TObjectList<TGifFrameItem>; { TGifReader } TGifReader = class(TObject) protected FHeader: TGIFHeader; FPalette: TPalette; FScreenWidth: Integer; FScreenHeight: Integer; FInterlace: Boolean; FBitsPerPixel: Byte; FBackgroundColorIndex: Byte; FResolution: Byte; FGifVer: TGifVer; public function Read(Stream: TStream; var AFrameList: TGifFrameList): Boolean; overload; virtual; function Read(FileName: string; var AFrameList: TGifFrameList): Boolean; overload; virtual; function ReadRes(Instance: THandle; ResName: string; ResType: PChar; var AFrameList: TGifFrameList): Boolean; overload; virtual; function ReadRes(Instance: THandle; ResId: Integer; ResType: PChar; var AFrameList: TGifFrameList): Boolean; overload; virtual; function Check(Stream: TStream): Boolean; overload; virtual; function Check(FileName: string): Boolean; overload; virtual; public constructor Create; virtual; destructor Destroy; override; property Header: TGIFHeader read FHeader; property ScreenWidth: Integer read FScreenWidth; property ScreenHeight: Integer read FScreenHeight; property Interlace: Boolean read FInterlace; property BitsPerPixel: Byte read FBitsPerPixel; property Background: Byte read FBackgroundColorIndex; property Resolution: Byte read FResolution; property GifVer: TGifVer read FGifVer; end; TGifFrameItem = class FDisposalMethod: Integer; FPos: TPoint; FTime: Integer; FDisbitmap: TBitmap; fBackColor : TalphaColor; public destructor Destroy; override; property Bitmap : TBitmap read FDisbitmap; end; implementation uses Math; function swap16(x: UInt16): UInt16; inline; begin Result := ((x and $FF) shl 8) or ((x and $FF00) shr 8); end; function swap32(x: UInt32): UInt32; inline; begin Result := ((x and $FF) shl 24) or ((x and $FF00) shl 8) or ((x and $FF0000) shr 8) or ((x and $FF000000) shr 24); end; function LEtoN(Value: word): word; overload; begin Result := swap16(Value); end; function LEtoN(Value: Dword): Dword; overload; begin Result := swap32(Value); end; procedure MergeBitmap(const Source, Dest: TBitmap; SrcRect: TRect; DestX, DestY: Integer); var I, J, MoveBytes: Integer; SrcData, DestData: TBitmapData; lpColorSrc, lpColorDst: PInternalColor; begin With Dest do begin if Map(TMapAccess.Write, DestData) then try if Source.Map(TMapAccess.Read, SrcData) then try if SrcRect.Left < 0 then begin Dec(DestX, SrcRect.Left); SrcRect.Left := 0; end; if SrcRect.Top < 0 then begin Dec(DestY, SrcRect.Top); SrcRect.Top := 0; end; SrcRect.Right := Min(SrcRect.Right, Source.Width); SrcRect.Bottom := Min(SrcRect.Bottom, Source.Height); if DestX < 0 then begin Dec(SrcRect.Left, DestX); DestX := 0; end; if DestY < 0 then begin Dec(SrcRect.Top, DestY); DestY := 0; end; if DestX + SrcRect.Width > Width then SrcRect.Width := Width - DestX; if DestY + SrcRect.Height > Height then SrcRect.Height := Height - DestY; if (SrcRect.Left < SrcRect.Right) and (SrcRect.Top < SrcRect.Bottom) then begin MoveBytes := SrcRect.Width * SrcData.BytesPerPixel; for I := 0 to SrcRect.Height - 1 do begin lpColorSrc := SrcData.GetPixelAddr(SrcRect.Left, SrcRect.Top + I); lpColorDst := DestData.GetPixelAddr(DestX, DestY + I); for J := 0 to SrcRect.Width - 1 do if lpColorSrc[J].A <> 0 then begin lpColorDst[J] := lpColorSrc[J]; end; end; end; finally Source.Unmap(SrcData); end; finally Unmap(DestData); end; end; end; { TGifReader } function TGifReader.Read(FileName: string; var AFrameList: TGifFrameList): Boolean; var fs: TFileStream; begin Result := False; fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); try Result := Read(fs, AFrameList); except end; fs.DisposeOf; end; function TGifReader.ReadRes(Instance: THandle; ResName: string; ResType: PChar; var AFrameList: TGifFrameList): Boolean; var res: TResourceStream; begin res := TResourceStream.Create(HInstance, ResName, ResType); Result := Read(res, AFrameList); res.DisposeOf; end; function TGifReader.ReadRes(Instance: THandle; ResId: Integer; ResType: PChar; var AFrameList: TGifFrameList): Boolean; var res: TResourceStream; begin res := TResourceStream.CreateFromID(HInstance, ResId, ResType); Result := Read(res, AFrameList); res.DisposeOf; end; function TGifReader.Read(Stream: TStream; var AFrameList: TGifFrameList): Boolean; var LDescriptor: TGifImageDescriptor; LGraphicsCtrlExt: TGifGraphicsControlExtension; LIsTransparent: Boolean; LGraphCtrlExt: Boolean; LFrameWidth: Integer; LFrameHeight: Integer; LLocalPalette: TPalette; LScanLineBuf: TBytes; procedure ReadPalette(Stream: TStream; Size: Integer; var APalette: TPalette); Var RGBEntry: TGifRGB; I: Integer; c: TInternalColor; begin SetLength(APalette, Size); For I := 0 To Size - 1 Do Begin Stream.Read(RGBEntry, SizeOf(RGBEntry)); With APalette[I] do begin R := RGBEntry.R or (RGBEntry.R shl 8); G := RGBEntry.G or (RGBEntry.G shl 8); B := RGBEntry.B or (RGBEntry.B shl 8); A := $FF; end; End; end; function ProcHeader: Boolean; var c: TInternalColor; begin Result := False; With FHeader do begin if (CompareMem(@Signature, @GifSignature, 3)) and (CompareMem(@Version, @VerSignature87a, 3)) or (CompareMem(@Version, @VerSignature89a, 3)) then begin FScreenWidth := FHeader.ScreenWidth; FScreenHeight := FHeader.ScreenHeight; FResolution := Packedbit and $70 shr 5 + 1; FBitsPerPixel := Packedbit and 7 + 1; FBackgroundColorIndex := BackgroundColor; if CompareMem(@Version, @VerSignature87a, 3) then FGifVer := ver87a else if CompareMem(@Version, @VerSignature89a, 3) then FGifVer := ver89a; Result := True; end else Raise Exception.Create('Unknown GIF image format'); end; end; function ProcFrame: Boolean; var LineSize: Integer; LBackColorIndex: Integer; begin Result := False; With LDescriptor do begin LFrameWidth := Width; LFrameHeight := Height; FInterlace := ((Packedbit and $40) = $40); end; if LGraphCtrlExt then begin LIsTransparent := (LGraphicsCtrlExt.Packedbit and $01) <> 0; If LIsTransparent then LBackColorIndex := LGraphicsCtrlExt.ColorIndex; end else begin LIsTransparent := FBackgroundColorIndex <> 0; LBackColorIndex := FBackgroundColorIndex; end; LineSize := LFrameWidth * (LFrameHeight + 1); SetLength(LScanLineBuf, LineSize); If LIsTransparent then begin LLocalPalette[LBackColorIndex].A := alphaTransparent; end; Result := True; end; function ReadAndProcBlock(Stream: TStream): Byte; var Introducer, Labels, SkipByte: Byte; begin Stream.Read(Introducer, 1); if Introducer = $21 then begin Stream.Read(Labels, 1); Case Labels of $FE, $FF: // Comment Extension block or Application Extension block while True do begin Stream.Read(SkipByte, 1); if SkipByte = 0 then Break; Stream.Seek(Int64( SkipByte), soFromCurrent); end; $F9: // Graphics Control Extension block begin Stream.Read(LGraphicsCtrlExt, SizeOf(LGraphicsCtrlExt)); LGraphCtrlExt := True; end; $01: // Plain Text Extension block begin Stream.Read(SkipByte, 1); Stream.Seek(Int64( SkipByte), soFromCurrent); while True do begin Stream.Read(SkipByte, 1); if SkipByte = 0 then Break; Stream.Seek(Int64( SkipByte), soFromCurrent); end; end; end; end; Result := Introducer; end; function ReadScanLine(Stream: TStream; AScanLine: PByte): Boolean; var OldPos, UnpackedSize, PackedSize: longint; I: Integer; Data, Bits, Code: Cardinal; SourcePtr: PByte; InCode: Cardinal; CodeSize: Cardinal; CodeMask: Cardinal; FreeCode: Cardinal; OldCode: Cardinal; Prefix: array [0 .. 4095] of Cardinal; Suffix, Stack: array [0 .. 4095] of Byte; StackPointer: PByte; Target: PByte; DataComp: TBytes; B, FInitialCodeSize, FirstChar: Byte; ClearCode, EOICode: word; begin DataComp := nil; try try Stream.Read(FInitialCodeSize, 1); OldPos := Stream.Position; PackedSize := 0; Repeat Stream.Read(B, 1); if B > 0 then begin Inc(PackedSize, B); Stream.Seek(Int64(B), soFromCurrent); CodeMask := (1 shl CodeSize) - 1; end; until B = 0; SetLength(DataComp, 2 * PackedSize); SourcePtr := @DataComp[0]; Stream.Position := OldPos; Repeat Stream.Read(B, 1); if B > 0 then begin Stream.ReadBuffer(SourcePtr^, B); Inc(SourcePtr, B); end; until B = 0; SourcePtr := @DataComp[0]; Target := AScanLine; CodeSize := FInitialCodeSize + 1; ClearCode := 1 shl FInitialCodeSize; EOICode := ClearCode + 1; FreeCode := ClearCode + 2; OldCode := 4096; CodeMask := (1 shl CodeSize) - 1; UnpackedSize := LFrameWidth * LFrameHeight; for I := 0 to ClearCode - 1 do begin Prefix[I] := 4096; Suffix[I] := I; end; StackPointer := @Stack; FirstChar := 0; Data := 0; Bits := 0; while (UnpackedSize > 0) and (PackedSize > 0) do begin Inc(Data, SourcePtr^ shl Bits); Inc(Bits, 8); while Bits >= CodeSize do begin Code := Data and CodeMask; Data := Data shr CodeSize; Dec(Bits, CodeSize); if Code = EOICode then Break; if Code = ClearCode then begin CodeSize := FInitialCodeSize + 1; CodeMask := (1 shl CodeSize) - 1; FreeCode := ClearCode + 2; OldCode := 4096; Continue; end; if Code > FreeCode then Break; if OldCode = 4096 then begin FirstChar := Suffix[Code]; Target^ := FirstChar; Inc(Target); Dec(UnpackedSize); OldCode := Code; Continue; end; InCode := Code; if Code = FreeCode then begin StackPointer^ := FirstChar; Inc(StackPointer); Code := OldCode; end; while Code > ClearCode do begin StackPointer^ := Suffix[Code]; Inc(StackPointer); Code := Prefix[Code]; end; FirstChar := Suffix[Code]; StackPointer^ := FirstChar; Inc(StackPointer); Prefix[FreeCode] := OldCode; Suffix[FreeCode] := FirstChar; if (FreeCode = CodeMask) and (CodeSize < 12) then begin Inc(CodeSize); CodeMask := (1 shl CodeSize) - 1; end; if FreeCode < 4095 then Inc(FreeCode); OldCode := InCode; repeat Dec(StackPointer); Target^ := StackPointer^; Inc(Target); Dec(UnpackedSize); until StackPointer = @Stack; end; Inc(SourcePtr); Dec(PackedSize); end; finally DataComp := nil; end; except end; Result := True; end; function WriteScanLine(var Img: TBitmap; AScanLine: PByte): Boolean; Var Row, Col: Integer; Pass, Every: Byte; P: PByte; function IsMultiple(NumberA, NumberB: Integer): Boolean; begin Result := (NumberA >= NumberB) and (NumberB > 0) and (NumberA mod NumberB = 0); end; var PLine: PInternalColor; Data: TBitmapData; begin Result := False; P := AScanLine; if Img.Map(TMapAccess.Write, Data) then begin try If FInterlace then begin For Pass := 1 to 4 do begin Case Pass of 1: begin Row := 0; Every := 8; end; 2: begin Row := 4; Every := 8; end; 3: begin Row := 2; Every := 4; end; 4: begin Row := 1; Every := 2; end; end; Repeat PLine := Data.GetScanline(Row); for Col := 0 to Img.Width - 1 do begin PLine[Col] := LLocalPalette[P^]; Inc(P); end; Inc(Row, Every); until Row >= Img.Height; end; end else begin for Row := 0 to Img.Height - 1 do begin PLine := Data.GetScanline(Row); for Col := 0 to Img.Width - 1 do begin PLine[Col] := LLocalPalette[P^]; Inc(P); end; end; end; Result := True; finally Img.Unmap(Data); end; end; end; procedure RenderFrame(const Index : integer; const aFrames : array of TGifFrameItem; const aDisplay : TBitmap); var I, First, Last: Integer; begin Last := Index; First := Max(0, Last); aDisplay.Clear(aFrames[Index].fBackColor); while First > 0 do begin if (fScreenWidth = aFrames[First].Bitmap.Width) and (fScreenHeight = aFrames[First].Bitmap.Height) then begin if (aFrames[First].FDisposalMethod = GIF_DISPOSAL_BACKGROUND) and (First < Last) then Break; end; Dec(First); end; for I := First to Last - 1 do begin case aFrames[I].FDisposalMethod of GIF_DISPOSAL_UNSPECIFIED, GIF_DISPOSAL_LEAVE: begin // Copy previous raw frame onto screen MergeBitmap(aFrames[i].Bitmap, aDisplay, aFrames[i].Bitmap.Bounds, aFrames[i].FPos.X, aFrames[i].FPos.Y); end; GIF_DISPOSAL_BACKGROUND: if (I > First) then begin // Restore background color aDisplay.ClearRect(TRectF.Create(aFrames[i].FPos.X, aFrames[i].FPos.Y, aFrames[i].FPos.X + aFrames[i].Bitmap.Width, aFrames[i].FPos.Y + aFrames[i].Bitmap.Height), aFrames[i].fBackColor); end; GIF_DISPOSAL_PREVIOUS: ; // Do nothing - previous state is already on screen end; end; MergeBitmap(aFrames[Index].Bitmap, aDisplay, aFrames[Index].Bitmap.Bounds, aFrames[Index].FPos.X, aFrames[Index].FPos.Y); end; var Introducer: Byte; ColorTableSize: Integer; tmp: TBitmap; LFrame: TGifFrameItem; FrameIndex: Integer; I: Integer; LBC : integer; LFrames : array of TGifFrameItem; rendered : array of TBitmap; begin Result := False; if not Check(Stream) then Exit; AFrameList.Clear; FGifVer := verUnknow; FPalette := nil; LScanLineBuf := nil; try Stream.Position := 0; Stream.Read(FHeader, SizeOf(FHeader)); {$IFDEF BIGENDIAN} with FHeader do begin ScreenWidth := LEtoN(ScreenWidth); ScreenHeight := LEtoN(ScreenHeight); end; {$ENDIF} if (FHeader.Packedbit and $80) = $80 then begin ColorTableSize := FHeader.Packedbit and 7 + 1; ReadPalette(Stream, 1 shl ColorTableSize, FPalette); end; if not ProcHeader then Exit; FrameIndex := 0; SetLength(LFrames, 0); while True do begin LLocalPalette := nil; Repeat Introducer := ReadAndProcBlock(Stream); until (Introducer in [$2C, $3B]); if Introducer = $3B then Break; Stream.Read(LDescriptor, SizeOf(LDescriptor)); {$IFDEF BIGENDIAN} with FDescriptor do begin Left := LEtoN(Left); Top := LEtoN(Top); Width := LEtoN(Width); Height := LEtoN(Height); end; {$ENDIF} if (LDescriptor.Packedbit and $80) <> 0 then begin ColorTableSize := LDescriptor.Packedbit and 7 + 1; ReadPalette(Stream, 1 shl ColorTableSize, LLocalPalette); end else begin LLocalPalette := Copy(FPalette, 0, Length(FPalette)); end; if not ProcFrame then Exit; LFrame := TGifFrameItem.Create; LFrame.FTime := 10 * LGraphicsCtrlExt.DelayTime; LFrame.FDisbitmap := TBitmap.Create(LFrameWidth, LFrameHeight); LFrame.FPos := Point(LDescriptor.Left, LDescriptor.Top); LFrame.FDisposalMethod := 7 and (LGraphicsCtrlExt.Packedbit shr 2); if not ReadScanLine(Stream, @LScanLineBuf[0]) then Exit; if not WriteScanLine(LFrame.FDisbitmap, @LScanLineBuf[0]) then Exit; if LGraphCtrlExt then begin LIsTransparent := (LGraphicsCtrlExt.Packedbit and $01) <> 0; If LIsTransparent then LBC := LGraphicsCtrlExt.ColorIndex else LBC := FBackgroundColorIndex; end else LBC := FBackgroundColorIndex; LFrame.fBackColor := LLocalPalette[LBC].Color; Inc(FrameIndex); SetLength(LFrames, FrameIndex); LFrames[FrameIndex - 1] := LFrame; end; SetLength(rendered, Length(LFrames)); for I := 0 to Length(LFrames) - 1 do begin tmp := TBitmap.Create(FScreenWidth, FScreenHeight); RenderFrame(I, LFrames, tmp); rendered[i] := tmp; end; for I := 0 to Length(LFrames) - 1 do begin LFrames[i].Bitmap.Assign(rendered[i]); FreeAndNil(rendered[i]); AFrameList.Add(LFrames[i]); end; Result := True; finally LLocalPalette := nil; LScanLineBuf := nil; rendered := nil; LFrames := nil; end; end; function TGifReader.Check(Stream: TStream): Boolean; var OldPos: Int64; begin try OldPos := Stream.Position; Stream.Read(FHeader, SizeOf(FHeader)); Result := (CompareMem(@FHeader.Signature, @GifSignature, 3)) and (CompareMem(@FHeader.Version, @VerSignature87a, 3)) or (CompareMem(@FHeader.Version, @VerSignature89a, 3)); Stream.Position := OldPos; except Result := False; end; end; function TGifReader.Check(FileName: string): Boolean; var fs: TFileStream; begin Result := False; fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); try Result := Check(fs); except end; fs.DisposeOf; end; constructor TGifReader.Create; begin inherited Create; end; destructor TGifReader.Destroy; begin inherited Destroy; end; { TGifFrameItem } destructor TGifFrameItem.Destroy; begin if FDisbitmap <> nil then begin FDisbitmap.DisposeOf; FDisbitmap := nil; end; inherited Destroy; end; end.
-
23 минуты назад, ENERGY сказал:
Так вы автору отдайте.
Если я не ошибаюсь, то автор давно на это забил.
-
Как оказалось, не устранил :(
-
Чем больше пользую, тем больше багов вылазит
Пытаюсь исправить. У этого класса есть проблемы с отображением черезстрочных (interlaced) гифок.
-
Кстати, обнаружилось еще пару багов в китайской либе для анимированных гифок...
Вроде бы устранил.
Вот листинг :
Спойлерunit FMX.GifUtils; interface uses System.Classes, System.SysUtils, System.Types, System.UITypes, FMX.Types, FMX.Objects, FMX.Graphics, System.Generics.Collections; const alphaTransparent = $00; GifSignature: array [0 .. 2] of Byte = ($47, $49, $46); // GIF VerSignature87a: array [0 .. 2] of Byte = ($38, $37, $61); // 87a VerSignature89a: array [0 .. 2] of Byte = ($38, $39, $61); // 89a GIF_DISPOSAL_UNSPECIFIED = 0; GIF_DISPOSAL_LEAVE = 1; GIF_DISPOSAL_BACKGROUND = 2; GIF_DISPOSAL_PREVIOUS = 3; type TGifVer = (verUnknow, ver87a, ver89a); TInternalColor = packed record case Integer of 0: ( {$IFDEF BIGENDIAN} R, G, B, A: Byte; {$ELSE} B, G, R, A: Byte; {$ENDIF} ); 1: (Color: TAlphaColor; ); end; {$POINTERMATH ON} PInternalColor = ^TInternalColor; {$POINTERMATH OFF} TGifRGB = packed record R: Byte; G: Byte; B: Byte; end; TGIFHeader = packed record Signature: array [0 .. 2] of Byte; // * Header Signature (always "GIF") */ Version: array [0 .. 2] of Byte; // * GIF format version("87a" or "89a") */ // Logical Screen Descriptor ScreenWidth: word; // * Width of Display Screen in Pixels */ ScreenHeight: word; // * Height of Display Screen in Pixels */ Packedbit: Byte; // * Screen and Color Map Information */ BackgroundColor: Byte; // * Background Color Index */ AspectRatio: Byte; // * Pixel Aspect Ratio */ end; TGifImageDescriptor = packed record Left: word; // * X position of image on the display */ Top: word; // * Y position of image on the display */ Width: word; // * Width of the image in pixels */ Height: word; // * Height of the image in pixels */ Packedbit: Byte; // * Image and Color Table Data Information */ end; TGifGraphicsControlExtension = packed record BlockSize: Byte; // * Size of remaining fields (always 04h) */ Packedbit: Byte; // * Method of graphics disposal to use */ DelayTime: word; // * Hundredths of seconds to wait */ ColorIndex: Byte; // * Transparent Color Index */ Terminator: Byte; // * Block Terminator (always 0) */ end; TGifReader = class; TPalette = TArray<TInternalColor>; TGifFrameItem = class; TGifFrameList = TObjectList<TGifFrameItem>; { TGifReader } TGifReader = class(TObject) protected FHeader: TGIFHeader; FPalette: TPalette; FScreenWidth: Integer; FScreenHeight: Integer; FInterlace: Boolean; FBitsPerPixel: Byte; FBackgroundColorIndex: Byte; FResolution: Byte; FGifVer: TGifVer; public function Read(Stream: TStream; var AFrameList: TGifFrameList): Boolean; overload; virtual; function Read(FileName: string; var AFrameList: TGifFrameList): Boolean; overload; virtual; function ReadRes(Instance: THandle; ResName: string; ResType: PChar; var AFrameList: TGifFrameList): Boolean; overload; virtual; function ReadRes(Instance: THandle; ResId: Integer; ResType: PChar; var AFrameList: TGifFrameList): Boolean; overload; virtual; function Check(Stream: TStream): Boolean; overload; virtual; function Check(FileName: string): Boolean; overload; virtual; public constructor Create; virtual; destructor Destroy; override; property Header: TGIFHeader read FHeader; property ScreenWidth: Integer read FScreenWidth; property ScreenHeight: Integer read FScreenHeight; property Interlace: Boolean read FInterlace; property BitsPerPixel: Byte read FBitsPerPixel; property Background: Byte read FBackgroundColorIndex; property Resolution: Byte read FResolution; property GifVer: TGifVer read FGifVer; end; TGifFrameItem = class FDisposalMethod: Integer; FPos: TPoint; FTime: Integer; FDisbitmap: TBitmap; public destructor Destroy; override; property Bitmap : TBitmap read FDisbitmap; end; implementation uses Math; function swap16(x: UInt16): UInt16; inline; begin Result := ((x and $FF) shl 8) or ((x and $FF00) shr 8); end; function swap32(x: UInt32): UInt32; inline; begin Result := ((x and $FF) shl 24) or ((x and $FF00) shl 8) or ((x and $FF0000) shr 8) or ((x and $FF000000) shr 24); end; function LEtoN(Value: word): word; overload; begin Result := swap16(Value); end; function LEtoN(Value: Dword): Dword; overload; begin Result := swap32(Value); end; procedure MergeBitmap(const Source, Dest: TBitmap; SrcRect: TRect; DestX, DestY: Integer); var I, J, MoveBytes: Integer; SrcData, DestData: TBitmapData; lpColorSrc, lpColorDst: PInternalColor; begin With Dest do begin if Map(TMapAccess.Write, DestData) then try if Source.Map(TMapAccess.Read, SrcData) then try if SrcRect.Left < 0 then begin Dec(DestX, SrcRect.Left); SrcRect.Left := 0; end; if SrcRect.Top < 0 then begin Dec(DestY, SrcRect.Top); SrcRect.Top := 0; end; SrcRect.Right := Min(SrcRect.Right, Source.Width); SrcRect.Bottom := Min(SrcRect.Bottom, Source.Height); if DestX < 0 then begin Dec(SrcRect.Left, DestX); DestX := 0; end; if DestY < 0 then begin Dec(SrcRect.Top, DestY); DestY := 0; end; if DestX + SrcRect.Width > Width then SrcRect.Width := Width - DestX; if DestY + SrcRect.Height > Height then SrcRect.Height := Height - DestY; if (SrcRect.Left < SrcRect.Right) and (SrcRect.Top < SrcRect.Bottom) then begin MoveBytes := SrcRect.Width * SrcData.BytesPerPixel; for I := 0 to SrcRect.Height - 1 do begin lpColorSrc := SrcData.GetPixelAddr(SrcRect.Left, SrcRect.Top + I); lpColorDst := DestData.GetPixelAddr(DestX, DestY + I); for J := 0 to SrcRect.Width - 1 do if lpColorSrc[J].A <> 0 then begin lpColorDst[J] := lpColorSrc[J]; end; end; end; finally Source.Unmap(SrcData); end; finally Unmap(DestData); end; end; end; { TGifReader } function TGifReader.Read(FileName: string; var AFrameList: TGifFrameList): Boolean; var fs: TFileStream; begin Result := False; fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); try Result := Read(fs, AFrameList); except end; fs.DisposeOf; end; function TGifReader.ReadRes(Instance: THandle; ResName: string; ResType: PChar; var AFrameList: TGifFrameList): Boolean; var res: TResourceStream; begin res := TResourceStream.Create(HInstance, ResName, ResType); Result := Read(res, AFrameList); res.DisposeOf; end; function TGifReader.ReadRes(Instance: THandle; ResId: Integer; ResType: PChar; var AFrameList: TGifFrameList): Boolean; var res: TResourceStream; begin res := TResourceStream.CreateFromID(HInstance, ResId, ResType); Result := Read(res, AFrameList); res.DisposeOf; end; function TGifReader.Read(Stream: TStream; var AFrameList: TGifFrameList): Boolean; var LDescriptor: TGifImageDescriptor; LGraphicsCtrlExt: TGifGraphicsControlExtension; LIsTransparent: Boolean; LGraphCtrlExt: Boolean; LFrameWidth: Integer; LFrameHeight: Integer; LLocalPalette: TPalette; LScanLineBuf: TBytes; procedure ReadPalette(Stream: TStream; Size: Integer; var APalette: TPalette); Var RGBEntry: TGifRGB; I: Integer; c: TInternalColor; begin SetLength(APalette, Size); For I := 0 To Size - 1 Do Begin Stream.Read(RGBEntry, SizeOf(RGBEntry)); With APalette[I] do begin R := RGBEntry.R or (RGBEntry.R shl 8); G := RGBEntry.G or (RGBEntry.G shl 8); B := RGBEntry.B or (RGBEntry.B shl 8); A := $FF; end; End; end; function ProcHeader: Boolean; var c: TInternalColor; begin Result := False; With FHeader do begin if (CompareMem(@Signature, @GifSignature, 3)) and (CompareMem(@Version, @VerSignature87a, 3)) or (CompareMem(@Version, @VerSignature89a, 3)) then begin FScreenWidth := FHeader.ScreenWidth; FScreenHeight := FHeader.ScreenHeight; FResolution := Packedbit and $70 shr 5 + 1; FBitsPerPixel := Packedbit and 7 + 1; FBackgroundColorIndex := BackgroundColor; if CompareMem(@Version, @VerSignature87a, 3) then FGifVer := ver87a else if CompareMem(@Version, @VerSignature89a, 3) then FGifVer := ver89a; Result := True; end else Raise Exception.Create('Unknown GIF image format'); end; end; function ProcFrame: Boolean; var LineSize: Integer; LBackColorIndex: Integer; begin Result := False; With LDescriptor do begin LFrameWidth := Width; LFrameHeight := Height; FInterlace := ((Packedbit and $40) = $40); end; if LGraphCtrlExt then begin LIsTransparent := (LGraphicsCtrlExt.Packedbit and $01) <> 0; If LIsTransparent then LBackColorIndex := LGraphicsCtrlExt.ColorIndex; end else begin LIsTransparent := FBackgroundColorIndex <> 0; LBackColorIndex := FBackgroundColorIndex; end; LineSize := LFrameWidth * (LFrameHeight + 1); SetLength(LScanLineBuf, LineSize); If LIsTransparent then begin LLocalPalette[LBackColorIndex].A := alphaTransparent; end; Result := True; end; function ReadAndProcBlock(Stream: TStream): Byte; var Introducer, Labels, SkipByte: Byte; begin Stream.Read(Introducer, 1); if Introducer = $21 then begin Stream.Read(Labels, 1); Case Labels of $FE, $FF: // Comment Extension block or Application Extension block while True do begin Stream.Read(SkipByte, 1); if SkipByte = 0 then Break; Stream.Seek(Int64( SkipByte), soFromCurrent); end; $F9: // Graphics Control Extension block begin Stream.Read(LGraphicsCtrlExt, SizeOf(LGraphicsCtrlExt)); LGraphCtrlExt := True; end; $01: // Plain Text Extension block begin Stream.Read(SkipByte, 1); Stream.Seek(Int64( SkipByte), soFromCurrent); while True do begin Stream.Read(SkipByte, 1); if SkipByte = 0 then Break; Stream.Seek(Int64( SkipByte), soFromCurrent); end; end; end; end; Result := Introducer; end; function ReadScanLine(Stream: TStream; AScanLine: PByte): Boolean; var OldPos, UnpackedSize, PackedSize: longint; I: Integer; Data, Bits, Code: Cardinal; SourcePtr: PByte; InCode: Cardinal; CodeSize: Cardinal; CodeMask: Cardinal; FreeCode: Cardinal; OldCode: Cardinal; Prefix: array [0 .. 4095] of Cardinal; Suffix, Stack: array [0 .. 4095] of Byte; StackPointer: PByte; Target: PByte; DataComp: TBytes; B, FInitialCodeSize, FirstChar: Byte; ClearCode, EOICode: word; begin DataComp := nil; try try Stream.Read(FInitialCodeSize, 1); OldPos := Stream.Position; PackedSize := 0; Repeat Stream.Read(B, 1); if B > 0 then begin Inc(PackedSize, B); Stream.Seek(Int64(B), soFromCurrent); CodeMask := (1 shl CodeSize) - 1; end; until B = 0; SetLength(DataComp, 2 * PackedSize); SourcePtr := @DataComp[0]; Stream.Position := OldPos; Repeat Stream.Read(B, 1); if B > 0 then begin Stream.ReadBuffer(SourcePtr^, B); Inc(SourcePtr, B); end; until B = 0; SourcePtr := @DataComp[0]; Target := AScanLine; CodeSize := FInitialCodeSize + 1; ClearCode := 1 shl FInitialCodeSize; EOICode := ClearCode + 1; FreeCode := ClearCode + 2; OldCode := 4096; CodeMask := (1 shl CodeSize) - 1; UnpackedSize := LFrameWidth * LFrameHeight; for I := 0 to ClearCode - 1 do begin Prefix[I] := 4096; Suffix[I] := I; end; StackPointer := @Stack; FirstChar := 0; Data := 0; Bits := 0; while (UnpackedSize > 0) and (PackedSize > 0) do begin Inc(Data, SourcePtr^ shl Bits); Inc(Bits, 8); while Bits >= CodeSize do begin Code := Data and CodeMask; Data := Data shr CodeSize; Dec(Bits, CodeSize); if Code = EOICode then Break; if Code = ClearCode then begin CodeSize := FInitialCodeSize + 1; CodeMask := (1 shl CodeSize) - 1; FreeCode := ClearCode + 2; OldCode := 4096; Continue; end; if Code > FreeCode then Break; if OldCode = 4096 then begin FirstChar := Suffix[Code]; Target^ := FirstChar; Inc(Target); Dec(UnpackedSize); OldCode := Code; Continue; end; InCode := Code; if Code = FreeCode then begin StackPointer^ := FirstChar; Inc(StackPointer); Code := OldCode; end; while Code > ClearCode do begin StackPointer^ := Suffix[Code]; Inc(StackPointer); Code := Prefix[Code]; end; FirstChar := Suffix[Code]; StackPointer^ := FirstChar; Inc(StackPointer); Prefix[FreeCode] := OldCode; Suffix[FreeCode] := FirstChar; if (FreeCode = CodeMask) and (CodeSize < 12) then begin Inc(CodeSize); CodeMask := (1 shl CodeSize) - 1; end; if FreeCode < 4095 then Inc(FreeCode); OldCode := InCode; repeat Dec(StackPointer); Target^ := StackPointer^; Inc(Target); Dec(UnpackedSize); until StackPointer = @Stack; end; Inc(SourcePtr); Dec(PackedSize); end; finally DataComp := nil; end; except end; Result := True; end; function WriteScanLine(var Img: TBitmap; AScanLine: PByte): Boolean; Var Row, Col: Integer; Pass, Every: Byte; P: PByte; function IsMultiple(NumberA, NumberB: Integer): Boolean; begin Result := (NumberA >= NumberB) and (NumberB > 0) and (NumberA mod NumberB = 0); end; var PLine: PInternalColor; Data: TBitmapData; begin Result := False; P := AScanLine; if Img.Map(TMapAccess.Write, Data) then begin try If FInterlace then begin For Pass := 1 to 4 do begin Case Pass of 1: begin Row := 0; Every := 8; end; 2: begin Row := 4; Every := 8; end; 3: begin Row := 2; Every := 4; end; 4: begin Row := 1; Every := 2; end; end; PLine := Data.GetScanline(Row); Repeat for Col := 0 to Img.Width - 1 do begin PLine[Col] := LLocalPalette[P^]; Inc(P); end; Inc(Row, Every); until Row >= Img.Height; end; end else begin for Row := 0 to Img.Height - 1 do begin PLine := Data.GetScanline(Row); for Col := 0 to Img.Width - 1 do begin PLine[Col] := LLocalPalette[P^]; Inc(P); end; end; end; Result := True; finally Img.Unmap(Data); end; end; end; var Introducer: Byte; ColorTableSize: Integer; tmp: TBitmap; LFrame: TGifFrameItem; FrameIndex: Integer; I: Integer; LBC : integer; begin Result := False; if not Check(Stream) then Exit; AFrameList.Clear; FGifVer := verUnknow; FPalette := nil; LScanLineBuf := nil; try Stream.Position := 0; Stream.Read(FHeader, SizeOf(FHeader)); {$IFDEF BIGENDIAN} with FHeader do begin ScreenWidth := LEtoN(ScreenWidth); ScreenHeight := LEtoN(ScreenHeight); end; {$ENDIF} if (FHeader.Packedbit and $80) = $80 then begin ColorTableSize := FHeader.Packedbit and 7 + 1; ReadPalette(Stream, 1 shl ColorTableSize, FPalette); end; if not ProcHeader then Exit; FrameIndex := 0; while True do begin LLocalPalette := nil; Repeat Introducer := ReadAndProcBlock(Stream); until (Introducer in [$2C, $3B]); if Introducer = $3B then Break; Stream.Read(LDescriptor, SizeOf(LDescriptor)); {$IFDEF BIGENDIAN} with FDescriptor do begin Left := LEtoN(Left); Top := LEtoN(Top); Width := LEtoN(Width); Height := LEtoN(Height); end; {$ENDIF} if (LDescriptor.Packedbit and $80) <> 0 then begin ColorTableSize := LDescriptor.Packedbit and 7 + 1; ReadPalette(Stream, 1 shl ColorTableSize, LLocalPalette); end else begin LLocalPalette := Copy(FPalette, 0, Length(FPalette)); end; if not ProcFrame then Exit; LFrame := TGifFrameItem.Create; LFrame.FTime := 10 * LGraphicsCtrlExt.DelayTime; LFrame.FDisbitmap := TBitmap.Create(FScreenWidth, FScreenHeight); tmp := TBitmap.Create(LFrameWidth, LFrameHeight); LFrame.FPos := Point(LDescriptor.Left, LDescriptor.Top); LFrame.FDisposalMethod := 7 and (LGraphicsCtrlExt.Packedbit shr 2); if (LFrameWidth <> FScreenWidth) or (LFrameHeight <> FScreenHeight) then LFrame.FDisposalMethod := GIF_DISPOSAL_LEAVE; if not ReadScanLine(Stream, @LScanLineBuf[0]) then Exit; if not WriteScanLine(tmp, @LScanLineBuf[0]) then Exit; if LGraphCtrlExt then begin LIsTransparent := (LGraphicsCtrlExt.Packedbit and $01) <> 0; If LIsTransparent then LBC := LGraphicsCtrlExt.ColorIndex else LBC := FBackgroundColorIndex; end else LBC := FBackgroundColorIndex; if FrameIndex = 0 then begin LFrame.FDisbitmap.Clear(LLocalPalette[LBC].Color); MergeBitmap(tmp, LFrame.FDisbitmap, Bounds(0, 0, LFrameWidth, LFrameHeight), LFrame.FPos.x, LFrame.FPos.Y); end else begin case AFrameList[AFrameList.Count - 1].FDisposalMethod of GIF_DISPOSAL_UNSPECIFIED, GIF_DISPOSAL_LEAVE: begin LFrame.FDisbitmap.CopyFromBitmap(AFrameList[AFrameList.Count - 1] .FDisbitmap); MergeBitmap(tmp, LFrame.FDisbitmap, Bounds(0, 0, LFrameWidth, LFrameHeight), LFrame.FPos.x, LFrame.FPos.Y); end; GIF_DISPOSAL_BACKGROUND: begin LFrame.FDisbitmap.Clear(LLocalPalette[LBC].Color); MergeBitmap(tmp, LFrame.FDisbitmap, Bounds(0, 0, LFrameWidth, LFrameHeight), LFrame.FPos.x, LFrame.FPos.Y); end; GIF_DISPOSAL_PREVIOUS: begin for I := AFrameList.Count - 1 downto 0 do begin if (AFrameList[I].FDisposalMethod = GIF_DISPOSAL_BACKGROUND) then Break; end; if I < 0 then I := 0; LFrame.FDisbitmap.CopyFromBitmap(AFrameList[I].FDisbitmap); MergeBitmap(tmp, LFrame.FDisbitmap, Bounds(0, 0, LFrameWidth, LFrameHeight), LFrame.FPos.x, LFrame.FPos.Y); end; 4 .. 7: begin end; end; end; AFrameList.Add(LFrame); tmp.DisposeOf; Inc(FrameIndex); end; Result := True; finally LLocalPalette := nil; LScanLineBuf := nil; end; end; function TGifReader.Check(Stream: TStream): Boolean; var OldPos: Int64; begin try OldPos := Stream.Position; Stream.Read(FHeader, SizeOf(FHeader)); Result := (CompareMem(@FHeader.Signature, @GifSignature, 3)) and (CompareMem(@FHeader.Version, @VerSignature87a, 3)) or (CompareMem(@FHeader.Version, @VerSignature89a, 3)); Stream.Position := OldPos; except Result := False; end; end; function TGifReader.Check(FileName: string): Boolean; var fs: TFileStream; begin Result := False; fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); try Result := Check(fs); except end; fs.DisposeOf; end; constructor TGifReader.Create; begin inherited Create; end; destructor TGifReader.Destroy; begin inherited Destroy; end; { TGifFrameItem } destructor TGifFrameItem.Destroy; begin if FDisbitmap <> nil then begin FDisbitmap.DisposeOf; FDisbitmap := nil; end; inherited Destroy; end; end.
-
1 час назад, Barbanel сказал:
Исходники не вложил. Я это имел в виду.
Да я понял.
-
Добрый день сообществу!
Выкладываю на суд первый релизик :)
Для создания паков со смайлами служит небольшая утилитка. Пока она позволяет только импортировать смайлы из гифок.
-
10 часов назад, asviridenkov сказал:
Можно и так считать. Разница в том, что одно решение сильно более универсальное, может отображать что угодно и оформление меняется через CSS как угодно, без написания кода.
Александр, я Вас прекрасно понимаю. Вы вложили много труда в свой продукт и хотите его продвигать. У Вас замечательный продукт, но мне не нужен весь функционал. А платить 340$ лишь за часть функционала я не готов.
Желаю Вам удачи в продвижении Вашего, несомненно, прекрасного и интересного продукта! Но давайте не будем разводить оффтоп.
-
В 04.05.2018 в 12:57, Вадим Смоленский сказал:
GIF_DISPOSAL_BACKGROUND: // 恢复到背景色 (Revert to background color) begin LFrame.FDisbitmap.Clear (LLocalPalette[FBackgroundColorIndex].Color); MergeBitmap(tmp, LFrame.FDisbitmap, Bounds(0, 0, LFrameWidth, LFrameHeight), LFrame.FPos.x, LFrame.FPos.Y); end;
В этом участке кода есть одна маленькая ошибка. Иногда это приводит к тому, что на некоторых кадрах теряется прозрачность.
Я заменил на вот такое :
GIF_DISPOSAL_BACKGROUND: begin LFrame.FDisbitmap.Clear(LLocalPalette[LGraphicsCtrlExt.ColorIndex].Color); MergeBitmap(tmp, LFrame.FDisbitmap, Bounds(0, 0, LFrameWidth, LFrameHeight), LFrame.FPos.x, LFrame.FPos.Y); end;
-
3 часа назад, asviridenkov сказал:
В составе HCL есть пример чата со смайлами, rich текстом, ссылками, картинками, автоматической конвертацией ссылок на картинки или на google maps в preview и т.д.. Канвасы там тоже нативные используются.
Это все замечательно, но чем оно мне поможет?
-
15 минут назад, Barbanel сказал:
Не зацикливайтесь сейчас на смайлах, делайте итеративно.
До релиза сойдет вставлять в мемо.текст bb-code или нечто подобное, а уже на канве выводить в этом месте смайл.
Сделайте релиз и потом уже постепенно прикручивайте плюшки типа смайлов в мемо.
Все имхо.Да я не зацикливаюсь. Смайлы в чате автоматически конвертируются из мнемоник в графику.
СпойлерISmile = interface ['{809B9246-71D8-4DE9-A559-2BB91ABD9CBE}'] procedure SetSprites(const Value : TBitmap); function GetSpritesCount: integer; function GetSprites: TBitmap; procedure SetSpritesCount(const Value: integer); procedure SetWidth(const Value: integer); procedure SetHeight(const Value: integer); function GetHeight: integer; function GetWidth: integer; procedure SetX(const Value : single); procedure SetY(const Value : single); function GetX: single; function GetY: single; function GetCurrentFrame: TBitmap; function GetFrame: TBitmap; function GetMnemonic: string; procedure SetMnemonic(const Value: string); function Clone: ISmile; property CurrentFrame : TBitmap read GetCurrentFrame; property Frame : TBitmap read GetFrame; property Sprites : TBitmap read GetSprites write SetSprites; property SpritesCount : integer read GetSpritesCount write SetSpritesCount; property Width : integer read GetWidth write SetWidth; property Height : integer read GetHeight write SetHeight; property Mnemonic : string read GetMnemonic write SetMnemonic; //position related to message property X : single read GetX write SetX; property Y : single read GetY write SetY; end; procedure TChatMessageObj.EstimateSize; var lSymW, mW : single; k : integer; lMsg : string; link : IChatURL; procedure EstimateInternal(const aSymWidth : single); var lTxt, lexeme : string; lines : TStringList; line : TMessageLine; obj : TLineObject; x, y, maxw : single; maxCol, lc, i, j, sp : integer; smile : ISmile; lTxtLayout : TTextLayout; begin lTxtLayout := TTextLayoutManager.DefaultTextLayout.Create(nil); lTxtLayout.BeginUpdate; lTxtLayout.WordWrap := false; lTxtLayout.TopLeft := TPointF.Zero; lTxtLayout.Trimming := TTextTrimming.None; lTxtLayout.MaxSize := TPointF.Create(65536, 65536); lTxtLayout.Font.Assign(fFont); lTxtLayout.Font.Size := fFont.Size; lTxtLayout.EndUpdate; lTxt := lMsg; for I := 0 to fLines.Count - 1 do fLines[i].Free; fLines.Clear; maxCol := round(fMaxSize.X / aSymWidth); lTxt := WrapMessage(lMsg, maxCol); lTxt := StringReplace(lTxt, sLineBreak + sLineBreak, sLineBreak, [rfReplaceAll]); lc := 0; lines := TStringList.Create; lines.Text := lTxt; lc := lines.Count; y := 0; maxw := 0; fSmiles.Clear; for I := 0 to lc - 1 do begin x := 0; line := TMessageLine.Create; line.X := x; line.Y := y; lTxt := lines[i]; repeat sp := FindFirstSmile(lTxt, smile); obj.X := x; obj.Y := y; if sp <> 0 then begin lexeme := lTxt; Delete(lexeme, sp, Length(lTxt) - sp + 1); obj.IsText := true; obj.Text := lexeme; lTxtLayout.Text := lexeme; obj.Width := lTxtLayout.TextRect.Width; obj.Height := lTxtLayout.TextRect.Height; Delete(lTxt, 1, Length(lexeme)); Delete(lTxt, 1, smile.Mnemonic.Length); line.Width := line.Width + obj.Width; if line.Height < fLineHeight then line.Height := fLineHeight; x := x + obj.Width; line.Objects.Add(obj); smile.X := x; smile.Y := y + 5; obj.Smile := nil; obj.IsText := false; obj.Text := ''; obj.Smile := smile; obj.Width := smile.Width; obj.Height := smile.Height; obj.X := x; line.Width := line.Width + obj.Width; if line.Height < smile.Height then line.Height := smile.Height; line.Objects.Add(obj); fSmiles.Add(smile); end else begin obj.IsText := true; obj.Text := lTxt; lTxtLayout.Text := lTxt; obj.Width := lTxtLayout.TextRect.Width; if line.Height < fLineHeight then line.Height := fLineHeight; line.Objects.Add(obj); line.Width := line.Width + obj.Width; end; x := x + obj.Width; if x > maxw then maxw := x; until sp = 0; fLines.Add(line); y := y + line.Height + 5; end; for I := 0 to fLines.Count - 1 do for j := 0 to fLines[i].Objects.Count - 1 do begin obj := fLines[i].Objects[j]; if not obj.IsText then obj.Smile.Y := obj.Y + fLines[i].Height - obj.Smile.Height + 5; fLines[i].Objects[j] := obj; end; fMsgSize.Width := maxw; fMsgSize.Height := y; FreeAndNil(lines); FreeAndNil(lTxtLayout); end; begin if fMsg = '' then Exit; //extract links here fLinks.Clear; lMsg := fMsg; k := 0; fHaveURL := false; repeat if IsURL(lMsg) then begin link := TMessageLink.Create; link.MaxWidth := fMaxSize.X; link.Font := fFont; link.URL := ExtractURL(lMsg, ''); fLinks.Add(link); fHaveURL := true; end; until not IsURL(lMsg); //estimate size without links lSymW := fAvgSymWidth; repeat EstimateInternal(lSymW); lSymW := lSymW + 0.75; until fMsgSize.Width <= fMaxSize.X; //place links if fLinks.Count > 0 then begin mW := 0; fMsgSize.Height := fMsgSize.Height + fLineHeight + 5; fLinksRect := TRectF.Create(0, fMsgSize.Height, 0, fMsgSize.Height); for k := 0 to fLinks.Count - 1 do begin fLinks[k].Position := TPointF.Create(0, fMsgSize.Height); if fLinks[k].Width > mW then mW := fLinks[k].Width; fMsgSize.Height := fMsgSize.Height + fLinks[k].Height; fLinksRect.Bottom := fLinksRect.Bottom + fLinks[k].Height; end; fLinksRect.Right := mW; fMsgSize.Width := Max(mW, fMsgSize.Width); end; end; function TChatMessageObj.FindFirstSmile(const aStr: string; out aSmile : ISmile): integer; begin Result := 0; if Assigned(fSmileProvider) then Result := fSmileProvider.ScanForSmile(aStr, aSmile) else aSmile := nil; end;
-
Теперь задача еще интересней. Как сделать мемо со смайлами?
-
-
Сообщения чата представлены вот таким интерфейсом
СпойлерfMessages : TList<IChatMessage>; IChatMessage = interface ['{8513A7CE-C16C-44D6-BE4A-B06973F9A969}'] function GetBackColor: TAlphaColor; function GetBubbleSize: TSizeF; function GetID: integer; function GetIsMy: boolean; function GetMsg: string; function GetMsgPos: TPointF; function GetMsgSize: TSizeF; function GetTxtColor: TAlphaColor; function GetUser: string; function GetUserNameHeight: single; function GetFont: TFont; function GetMaxSize: TPointF; function GetSmiles: TList<ISmile>; function GetLinks: TList<IChatURL>; function GetHasSmiles: boolean; function GetHasURL: boolean; function GetSmileProvider: ISmileProvider; function GetSelected: boolean; procedure SetBackColor(const Value: TAlphaColor); procedure SetBubbleSize(const Value: TSizeF); procedure SetID(const Value: integer); procedure SetIsMy(const Value: boolean); procedure SetMsg(const Value: string); procedure SetMsgPos(const Value: TPointF); procedure SetMsgSize(const Value: TSizeF); procedure SetTxtColor(const Value: TAlphaColor); procedure SetUser(const Value: string); procedure SetUserNameHeight(const Value: single); procedure SetFont(const Value: TFont); procedure SetMaxSize(const Value: TPointF); procedure SetAvgSymWidth(const Value: single); procedure SetLineHeight(const Value: single); procedure SetSmileProvider(const Value: ISmileProvider); procedure SetSelected(const Value : boolean); function IsPointInMessage(const aPoint : TPointF): boolean; function GetLinkByPoint(const aPoint : TPointF): IChatURL; procedure RenderMessage(const aRect, aClipRect : TRectF; const aCanvas : INativeCanvas); property Msg : string read GetMsg write SetMsg; property User : string read GetUser write SetUser; property ID : integer read GetID write SetID; property IsMy : boolean read GetIsMy write SetIsMy; property BubbleSize : TSizeF read GetBubbleSize write SetBubbleSize; property MsgSize : TSizeF read GetMsgSize write SetMsgSize; property MsgPos : TPointF read GetMsgPos write SetMsgPos; property UserNameHeight : single read GetUserNameHeight write SetUserNameHeight; property BackColor : TAlphaColor read GetBackColor write SetBackColor; property TxtColor : TAlphaColor read GetTxtColor write SetTxtColor; property MaxSize : TPointF read GetMaxSize write SetMaxSize; property Font : TFont read GetFont write SetFont; property Smiles : TList<ISmile> read GetSmiles; property Links : TList<IChatURL> read GetLinks; property HasSmiles : boolean read GetHasSmiles; property HasURL : boolean read GetHasURL; property AvgSymWidth : single write SetAvgSymWidth; property LineHeight : single write SetLineHeight; property SmileProvider : ISmileProvider read GetSmileProvider write SetSmileProvider; property Selected : boolean read GetSelected write SetSelected; end;
-
Да, пожалуйста! Не уверен на сколько это поможет сообществу.
Спойлерprocedure TIVGChatViewer.Paint; var r : TRectF; ss : TCanvasSaveState; nCanvas : INativeCanvas; begin inherited; if csDesigning in ComponentState then begin r := ClipRect; r.Inflate(-1, -1); Canvas.BeginScene; Canvas.Stroke.Dash := TStrokeDash.Dash; Canvas.Stroke.Color := claBlack; Canvas.DrawRect(r, 0, 0, [TCorner.TopLeft, TCorner.TopRight, TCorner.BottomLeft, TCorner.BottomRight], 1); Canvas.Font.Size := 16; Canvas.Fill.Color := claBlack; Canvas.FillText(r, Name, false, 1, [], TTextAlign.Center, TTextAlign.Center); Canvas.EndScene; Exit; end; if Assigned(Canvas) then begin ss := Canvas.SaveState; begin fMessagesToShow.Clear; nCanvas := Canvas.ToNativeCanvas(TDrawMethod.Native); nCanvas.Font.Assign(Canvas.Font); nCanvas.NativeDraw(LocalRect, procedure var i : integer; canBreak : boolean; begin canBreak := false; for I := fMessages.Count - 1 downto 0 do if IsMessageInViewport(fMessages[i]) then begin canBreak := true; r.Top := fMessages[i].MsgPos.Y - fViewPort; r.Bottom := r.Top + fMessages[i].BubbleSize.Height; r.Left := fMessages[i].MsgPos.X; r.Right := fMessages[i].MsgPos.X + fMessages[i].BubbleSize.Width; nCanvas.Fill.Color := fMessages[i].BackColor; PaintBubble(r, fMessages[i].IsMy, fMessages[i].Selected, nCanvas); if fMessages[i].IsMy then r.Left := r.Left + 5 else r.Left := r.Left + 15; r.Right := r.Left + fMessages[i].MsgSize.Width; r.Bottom := r.Top + fMessages[i].UserNameHeight; nCanvas.Font.Assign(fHeaderFont); nCanvas.Fill.Color := claWhite; nCanvas.FillText(r, fMessages[i].User, false, 1, [], TTextAlign.Leading, TTextAlign.Trailing); r.Top := r.Bottom + 5; r.Left := r.Left + 5; r.Right := r.Left + fMessages[i].MsgSize.Width; r.Bottom := r.Top + fMessages[i].MsgSize.Height; nCanvas.Font.Style := nCanvas.Font.Style - [TFontStyle.fsBold]; nCanvas.Fill.Color := fMessages[i].TxtColor; fMessages[i].RenderMessage(r, ClipRect, nCanvas); fMessagesToShow.Add(fMessages[i]); end else if canBreak then Break; for i := 0 to fMessagesToShow.Count - 1 do PaintSmiles(fMessagesToShow[i], nCanvas, fAniEnabled); end); end; Canvas.RestoreState(ss); end; fAniEnabled := true; end;
В 11.06.2018 в 19:16, ENERGY сказал:Т.е. вы отрисовываете текст на битмапе через nativeDraw , а затем его отображаете?
Нет, сразу через NativeDraw. Оно само на битмапе рисует.
-
Ура! Вышла 10.3 Rio!
в Информация о версиях RAD Studio
Опубликовано
А что с компилятором для Linux?