| 1 | unit BGRADithering;
|
|---|
| 2 |
|
|---|
| 3 | {$mode objfpc}{$H+}
|
|---|
| 4 |
|
|---|
| 5 | interface
|
|---|
| 6 |
|
|---|
| 7 | uses
|
|---|
| 8 | Classes, SysUtils, BGRAFilterType, BGRAPalette, BGRABitmapTypes;
|
|---|
| 9 |
|
|---|
| 10 | type
|
|---|
| 11 | TOutputPixelProc = procedure(X,Y: NativeInt; AColorIndex: NativeInt; AColor: TBGRAPixel) of object;
|
|---|
| 12 |
|
|---|
| 13 | { TDitheringTask }
|
|---|
| 14 |
|
|---|
| 15 | TDitheringTask = class(TFilterTask)
|
|---|
| 16 | protected
|
|---|
| 17 | FBounds: TRect;
|
|---|
| 18 | FIgnoreAlpha: boolean;
|
|---|
| 19 | FPalette: TBGRACustomApproxPalette;
|
|---|
| 20 | FCurrentOutputScanline: PBGRAPixel;
|
|---|
| 21 | FCurrentOutputY: NativeInt;
|
|---|
| 22 | FOutputPixel : TOutputPixelProc;
|
|---|
| 23 | FDrawMode: TDrawMode;
|
|---|
| 24 | procedure OutputPixel(X,Y: NativeInt; {%H-}AColorIndex: NativeInt; AColor: TBGRAPixel); virtual;
|
|---|
| 25 | procedure ApproximateColor(const AColor: TBGRAPixel; out AApproxColor: TBGRAPixel; out AIndex: integer);
|
|---|
| 26 | public
|
|---|
| 27 | constructor Create(ASource: IBGRAScanner; APalette: TBGRACustomApproxPalette; ADestination: TBGRACustomBitmap; AIgnoreAlpha: boolean; ABounds: TRect); overload;
|
|---|
| 28 | constructor Create(bmp: TBGRACustomBitmap; APalette: TBGRACustomApproxPalette; AInPlace: boolean; AIgnoreAlpha: boolean; ABounds: TRect); overload;
|
|---|
| 29 | constructor Create(bmp: TBGRACustomBitmap; APalette: TBGRACustomApproxPalette; AInPlace: boolean; AIgnoreAlpha: boolean); overload;
|
|---|
| 30 | property OnOutputPixel: TOutputPixelProc read FOutputPixel write FOutputPixel;
|
|---|
| 31 | property DrawMode: TDrawMode read FDrawMode write FDrawMode;
|
|---|
| 32 | end;
|
|---|
| 33 |
|
|---|
| 34 | { TNearestColorTask }
|
|---|
| 35 |
|
|---|
| 36 | TNearestColorTask = class(TDitheringTask)
|
|---|
| 37 | protected
|
|---|
| 38 | procedure DoExecute; override;
|
|---|
| 39 | end;
|
|---|
| 40 |
|
|---|
| 41 | { TFloydSteinbergDitheringTask }
|
|---|
| 42 |
|
|---|
| 43 | TFloydSteinbergDitheringTask = class(TDitheringTask)
|
|---|
| 44 | protected
|
|---|
| 45 | procedure DoExecute; override;
|
|---|
| 46 | end;
|
|---|
| 47 |
|
|---|
| 48 | { TDitheringToIndexedImage }
|
|---|
| 49 |
|
|---|
| 50 | TDitheringToIndexedImage = class
|
|---|
| 51 | protected
|
|---|
| 52 | FBitOrder: TRawImageBitOrder;
|
|---|
| 53 | FByteOrder: TRawImageByteOrder;
|
|---|
| 54 | FBitsPerPixel: integer;
|
|---|
| 55 | FLineOrder: TRawImageLineOrder;
|
|---|
| 56 | FPalette: TBGRACustomApproxPalette;
|
|---|
| 57 | FIgnoreAlpha: boolean;
|
|---|
| 58 | FTransparentColorIndex: NativeInt;
|
|---|
| 59 |
|
|---|
| 60 | //following variables are used during dithering
|
|---|
| 61 | FCurrentScanlineSize: PtrInt;
|
|---|
| 62 | FCurrentData: PByte;
|
|---|
| 63 | FCurrentOutputY: NativeInt;
|
|---|
| 64 | FCurrentOutputScanline: PByte;
|
|---|
| 65 | FCurrentBitOrderMask: NativeInt;
|
|---|
| 66 | FCurrentMaxY: NativeInt;
|
|---|
| 67 |
|
|---|
| 68 | procedure SetPalette(AValue: TBGRACustomApproxPalette);
|
|---|
| 69 | procedure SetIgnoreAlpha(AValue: boolean);
|
|---|
| 70 | procedure SetLineOrder(AValue: TRawImageLineOrder);
|
|---|
| 71 | procedure SetBitOrder(AValue: TRawImageBitOrder); virtual;
|
|---|
| 72 | procedure SetBitsPerPixel(AValue: integer); virtual;
|
|---|
| 73 | procedure SetByteOrder(AValue: TRawImageByteOrder); virtual;
|
|---|
| 74 | procedure OutputPixelSubByte(X,Y: NativeInt; AColorIndex: NativeInt; {%H-}AColor: TBGRAPixel); virtual;
|
|---|
| 75 | procedure OutputPixelFullByte(X,Y: NativeInt; AColorIndex: NativeInt; {%H-}AColor: TBGRAPixel); virtual;
|
|---|
| 76 | function GetScanline(Y: NativeInt): Pointer; virtual;
|
|---|
| 77 | function GetTransparentColorIndex: integer;
|
|---|
| 78 | procedure SetTransparentColorIndex(AValue: integer);
|
|---|
| 79 | public
|
|---|
| 80 | constructor Create(APalette: TBGRACustomApproxPalette; AIgnoreAlpha: boolean; ABitsPerPixelForIndices: integer); overload; //use platform byte order
|
|---|
| 81 | constructor Create(APalette: TBGRACustomApproxPalette; AIgnoreAlpha: boolean; ABitsPerPixelForIndices: integer; AByteOrder: TRawImageByteOrder); overload; //maybe necessary if larger than 8 bits per pixel
|
|---|
| 82 |
|
|---|
| 83 | function DitherImage(AAlgorithm: TDitheringAlgorithm; AImage: TBGRACustomBitmap): Pointer; overload; //use minimum scanline size
|
|---|
| 84 | function DitherImage(AAlgorithm: TDitheringAlgorithm; AImage: TBGRACustomBitmap; AScanlineSize: PtrInt): Pointer; overload;
|
|---|
| 85 | procedure DitherImageTo(AAlgorithm: TDitheringAlgorithm; AImage: TBGRACustomBitmap; AData: Pointer); overload; //use minimum scanline size
|
|---|
| 86 | procedure DitherImageTo(AAlgorithm: TDitheringAlgorithm; AImage: TBGRACustomBitmap; AData: Pointer; AScanlineSize: PtrInt); overload;
|
|---|
| 87 | function ComputeMinimumScanlineSize(AWidthInPixels: integer): PtrInt;
|
|---|
| 88 | function AllocateSpaceForIndexedData(AImage: TBGRACustomBitmap; AScanlineSize: PtrInt): pointer;
|
|---|
| 89 |
|
|---|
| 90 | //optional customization of format
|
|---|
| 91 | property BitsPerPixel: integer read FBitsPerPixel write SetBitsPerPixel;
|
|---|
| 92 | property BitOrder: TRawImageBitOrder read FBitOrder write SetBitOrder;
|
|---|
| 93 | property ByteOrder: TRawImageByteOrder read FByteOrder write SetByteOrder;
|
|---|
| 94 | property LineOrder: TRawImageLineOrder read FLineOrder write SetLineOrder;
|
|---|
| 95 |
|
|---|
| 96 | property Palette: TBGRACustomApproxPalette read FPalette write SetPalette;
|
|---|
| 97 | property IgnoreAlpha: boolean read FIgnoreAlpha write SetIgnoreAlpha;
|
|---|
| 98 |
|
|---|
| 99 | //when there is no transparent color in the palette, or that IgnoreAlpha is set to True,
|
|---|
| 100 | //this allows to define the index for the fully transparent color
|
|---|
| 101 | property DefaultTransparentColorIndex: integer read GetTransparentColorIndex write SetTransparentColorIndex;
|
|---|
| 102 | end;
|
|---|
| 103 |
|
|---|
| 104 | function CreateDitheringTask(AAlgorithm: TDitheringAlgorithm; ABitmap: TBGRACustomBitmap; APalette: TBGRACustomApproxPalette;
|
|---|
| 105 | AIgnoreAlpha: boolean): TDitheringTask; overload;
|
|---|
| 106 | function CreateDitheringTask(AAlgorithm: TDitheringAlgorithm; ABitmap: TBGRACustomBitmap; APalette: TBGRACustomApproxPalette;
|
|---|
| 107 | AIgnoreAlpha: boolean; ABounds: TRect): TDitheringTask; overload;
|
|---|
| 108 | function CreateDitheringTask(AAlgorithm: TDitheringAlgorithm; ASource: IBGRAScanner; ADestination: TBGRACustomBitmap; ABounds: TRect): TDitheringTask; overload;
|
|---|
| 109 | function CreateDitheringTask(AAlgorithm: TDitheringAlgorithm; ASource: IBGRAScanner; ADestination: TBGRACustomBitmap; APalette: TBGRACustomApproxPalette;
|
|---|
| 110 | AIgnoreAlpha: boolean; ABounds: TRect): TDitheringTask; overload;
|
|---|
| 111 |
|
|---|
| 112 | function DitherImageTo16Bit(AAlgorithm: TDitheringAlgorithm; ABitmap: TBGRACustomBitmap): TBGRACustomBitmap;
|
|---|
| 113 |
|
|---|
| 114 | implementation
|
|---|
| 115 |
|
|---|
| 116 | uses BGRABlend;
|
|---|
| 117 |
|
|---|
| 118 | function AbsRGBADiff(const c1, c2: TExpandedPixel): NativeInt;
|
|---|
| 119 | begin
|
|---|
| 120 | result := abs(c1.alpha-c2.alpha);
|
|---|
| 121 | result += abs(c1.red-c2.red);
|
|---|
| 122 | result += abs(c1.green-c2.green);
|
|---|
| 123 | result += abs(c1.blue-c2.blue);
|
|---|
| 124 | end;
|
|---|
| 125 |
|
|---|
| 126 | function CreateDitheringTask(AAlgorithm: TDitheringAlgorithm; ABitmap: TBGRACustomBitmap; APalette: TBGRACustomApproxPalette;
|
|---|
| 127 | AIgnoreAlpha: boolean): TDitheringTask;
|
|---|
| 128 | begin
|
|---|
| 129 | result := CreateDitheringTask(AAlgorithm, ABitmap, APalette, AIgnoreAlpha, rect(0,0,ABitmap.width, ABitmap.Height));
|
|---|
| 130 | end;
|
|---|
| 131 |
|
|---|
| 132 | function CreateDitheringTask(AAlgorithm: TDitheringAlgorithm; ABitmap: TBGRACustomBitmap; APalette: TBGRACustomApproxPalette;
|
|---|
| 133 | AIgnoreAlpha: boolean; ABounds: TRect): TDitheringTask;
|
|---|
| 134 | begin
|
|---|
| 135 | result := nil;
|
|---|
| 136 | case AAlgorithm of
|
|---|
| 137 | daNearestNeighbor: result := TNearestColorTask.Create(ABitmap, APalette, False, AIgnoreAlpha, ABounds);
|
|---|
| 138 | daFloydSteinberg: result := TFloydSteinbergDitheringTask.Create(ABitmap, APalette, False, AIgnoreAlpha, ABounds);
|
|---|
| 139 | else raise exception.Create('Unknown algorithm');
|
|---|
| 140 | end;
|
|---|
| 141 | end;
|
|---|
| 142 |
|
|---|
| 143 | function CreateDitheringTask(AAlgorithm: TDitheringAlgorithm;
|
|---|
| 144 | ASource: IBGRAScanner; ADestination: TBGRACustomBitmap; ABounds: TRect
|
|---|
| 145 | ): TDitheringTask;
|
|---|
| 146 | begin
|
|---|
| 147 | result := CreateDitheringTask(AAlgorithm, ASource, ADestination, nil, true, ABounds);
|
|---|
| 148 | end;
|
|---|
| 149 |
|
|---|
| 150 | function CreateDitheringTask(AAlgorithm: TDitheringAlgorithm;
|
|---|
| 151 | ASource: IBGRAScanner; ADestination: TBGRACustomBitmap;
|
|---|
| 152 | APalette: TBGRACustomApproxPalette; AIgnoreAlpha: boolean; ABounds: TRect
|
|---|
| 153 | ): TDitheringTask;
|
|---|
| 154 | begin
|
|---|
| 155 | result := nil;
|
|---|
| 156 | case AAlgorithm of
|
|---|
| 157 | daNearestNeighbor: result := TNearestColorTask.Create(ASource, APalette, ADestination, AIgnoreAlpha, ABounds);
|
|---|
| 158 | daFloydSteinberg: result := TFloydSteinbergDitheringTask.Create(ASource, APalette, ADestination, AIgnoreAlpha, ABounds);
|
|---|
| 159 | else raise exception.Create('Unknown algorithm');
|
|---|
| 160 | end;
|
|---|
| 161 | end;
|
|---|
| 162 |
|
|---|
| 163 | function DitherImageTo16Bit(AAlgorithm: TDitheringAlgorithm;
|
|---|
| 164 | ABitmap: TBGRACustomBitmap): TBGRACustomBitmap;
|
|---|
| 165 | var
|
|---|
| 166 | palette16bit: TBGRA16BitPalette;
|
|---|
| 167 | dither: TDitheringTask;
|
|---|
| 168 | begin
|
|---|
| 169 | palette16bit := TBGRA16BitPalette.Create;
|
|---|
| 170 | dither := CreateDitheringTask(AAlgorithm, ABitmap, palette16bit, false);
|
|---|
| 171 | result := dither.Execute;
|
|---|
| 172 | dither.Free;
|
|---|
| 173 | palette16bit.Free;
|
|---|
| 174 | end;
|
|---|
| 175 |
|
|---|
| 176 | { TDitheringToIndexedImage }
|
|---|
| 177 |
|
|---|
| 178 | procedure TDitheringToIndexedImage.SetBitsPerPixel(AValue: integer);
|
|---|
| 179 | begin
|
|---|
| 180 | if not (AValue in [1,2,4,8,16,32]) then
|
|---|
| 181 | raise exception.Create('Invalid value for bits per pixel. Allowed values: 1,2,4,8,16,32.');
|
|---|
| 182 | if FBitsPerPixel=AValue then Exit;
|
|---|
| 183 | FBitsPerPixel:=AValue;
|
|---|
| 184 | end;
|
|---|
| 185 |
|
|---|
| 186 | procedure TDitheringToIndexedImage.SetByteOrder(AValue: TRawImageByteOrder);
|
|---|
| 187 | begin
|
|---|
| 188 | if FByteOrder=AValue then Exit;
|
|---|
| 189 | FByteOrder:=AValue;
|
|---|
| 190 | end;
|
|---|
| 191 |
|
|---|
| 192 | procedure TDitheringToIndexedImage.OutputPixelSubByte(X, Y: NativeInt;
|
|---|
| 193 | AColorIndex: NativeInt; AColor: TBGRAPixel);
|
|---|
| 194 | var p: PByte;
|
|---|
| 195 | begin
|
|---|
| 196 | if y <> FCurrentOutputY then
|
|---|
| 197 | begin
|
|---|
| 198 | FCurrentOutputY := y;
|
|---|
| 199 | FCurrentOutputScanline := GetScanline(Y);
|
|---|
| 200 | end;
|
|---|
| 201 | if AColorIndex = -1 then AColorIndex := FTransparentColorIndex;
|
|---|
| 202 | case FBitsPerPixel of
|
|---|
| 203 | 1: begin
|
|---|
| 204 | p := FCurrentOutputScanline+(x shr 3);
|
|---|
| 205 | p^ := p^ or ((AColorIndex and 1) shl ((x xor FCurrentBitOrderMask) and 7));
|
|---|
| 206 | end;
|
|---|
| 207 | 2: begin
|
|---|
| 208 | p := FCurrentOutputScanline+(x shr 2);
|
|---|
| 209 | p^ := p^ or ((AColorIndex and 1) shl (((x xor FCurrentBitOrderMask) and 3) shl 1));
|
|---|
| 210 | end;
|
|---|
| 211 | 4: begin
|
|---|
| 212 | p := FCurrentOutputScanline+(x shr 1);
|
|---|
| 213 | p^ := p^ or ((AColorIndex and 1) shl (((x xor FCurrentBitOrderMask) and 1) shl 2));
|
|---|
| 214 | end;
|
|---|
| 215 | end;
|
|---|
| 216 | end;
|
|---|
| 217 |
|
|---|
| 218 | procedure TDitheringToIndexedImage.OutputPixelFullByte(X, Y: NativeInt;
|
|---|
| 219 | AColorIndex: NativeInt; AColor: TBGRAPixel);
|
|---|
| 220 | begin
|
|---|
| 221 | if y <> FCurrentOutputY then
|
|---|
| 222 | begin
|
|---|
| 223 | FCurrentOutputY := y;
|
|---|
| 224 | FCurrentOutputScanline := GetScanline(Y);
|
|---|
| 225 | end;
|
|---|
| 226 | if AColorIndex = -1 then AColorIndex := FTransparentColorIndex;
|
|---|
| 227 | case FBitsPerPixel of
|
|---|
| 228 | 8: (FCurrentOutputScanline+x)^ := AColorIndex;
|
|---|
| 229 | 16: (PWord(FCurrentOutputScanline)+x)^ := AColorIndex;
|
|---|
| 230 | 32: (PDWord(FCurrentOutputScanline)+x)^ := AColorIndex;
|
|---|
| 231 | end;
|
|---|
| 232 | end;
|
|---|
| 233 |
|
|---|
| 234 | function TDitheringToIndexedImage.GetScanline(Y: NativeInt): Pointer;
|
|---|
| 235 | begin
|
|---|
| 236 | if FLineOrder = riloTopToBottom then
|
|---|
| 237 | result := FCurrentData + Y*FCurrentScanlineSize
|
|---|
| 238 | else
|
|---|
| 239 | result := FCurrentData + (FCurrentMaxY-Y)*FCurrentScanlineSize
|
|---|
| 240 | end;
|
|---|
| 241 |
|
|---|
| 242 | procedure TDitheringToIndexedImage.SetIgnoreAlpha(AValue: boolean);
|
|---|
| 243 | begin
|
|---|
| 244 | if FIgnoreAlpha=AValue then Exit;
|
|---|
| 245 | FIgnoreAlpha:=AValue;
|
|---|
| 246 | end;
|
|---|
| 247 |
|
|---|
| 248 | procedure TDitheringToIndexedImage.SetTransparentColorIndex(AValue: integer);
|
|---|
| 249 | begin
|
|---|
| 250 | if FTransparentColorIndex=AValue then Exit;
|
|---|
| 251 | FTransparentColorIndex:=AValue;
|
|---|
| 252 | end;
|
|---|
| 253 |
|
|---|
| 254 | function TDitheringToIndexedImage.GetTransparentColorIndex: integer;
|
|---|
| 255 | begin
|
|---|
| 256 | result := FTransparentColorIndex;
|
|---|
| 257 | end;
|
|---|
| 258 |
|
|---|
| 259 | procedure TDitheringToIndexedImage.SetPalette(AValue: TBGRACustomApproxPalette);
|
|---|
| 260 | begin
|
|---|
| 261 | if FPalette=AValue then Exit;
|
|---|
| 262 | FPalette:=AValue;
|
|---|
| 263 | end;
|
|---|
| 264 |
|
|---|
| 265 | procedure TDitheringToIndexedImage.SetLineOrder(AValue: TRawImageLineOrder);
|
|---|
| 266 | begin
|
|---|
| 267 | if FLineOrder=AValue then Exit;
|
|---|
| 268 | FLineOrder:=AValue;
|
|---|
| 269 | end;
|
|---|
| 270 |
|
|---|
| 271 | procedure TDitheringToIndexedImage.SetBitOrder(AValue: TRawImageBitOrder);
|
|---|
| 272 | begin
|
|---|
| 273 | if FBitOrder=AValue then Exit;
|
|---|
| 274 | FBitOrder:=AValue;
|
|---|
| 275 | end;
|
|---|
| 276 |
|
|---|
| 277 | constructor TDitheringToIndexedImage.Create(APalette: TBGRACustomApproxPalette; AIgnoreAlpha: boolean; ABitsPerPixelForIndices: integer);
|
|---|
| 278 | begin
|
|---|
| 279 | BitsPerPixel:= ABitsPerPixelForIndices;
|
|---|
| 280 | BitOrder := riboReversedBits; //convention in BMP format
|
|---|
| 281 | {$IFDEF ENDIAN_LITTLE}
|
|---|
| 282 | ByteOrder:= riboLSBFirst;
|
|---|
| 283 | {$ELSE}
|
|---|
| 284 | ByteOrder:= riboMSBFirst;
|
|---|
| 285 | {$ENDIF}
|
|---|
| 286 | Palette := APalette;
|
|---|
| 287 | IgnoreAlpha:= AIgnoreAlpha;
|
|---|
| 288 | end;
|
|---|
| 289 |
|
|---|
| 290 | constructor TDitheringToIndexedImage.Create(APalette: TBGRACustomApproxPalette; AIgnoreAlpha: boolean; ABitsPerPixelForIndices: integer;
|
|---|
| 291 | AByteOrder: TRawImageByteOrder);
|
|---|
| 292 | begin
|
|---|
| 293 | BitsPerPixel:= ABitsPerPixelForIndices;
|
|---|
| 294 | BitOrder := riboReversedBits; //convention in BMP format
|
|---|
| 295 | ByteOrder:= AByteOrder;
|
|---|
| 296 | Palette := APalette;
|
|---|
| 297 | IgnoreAlpha:= AIgnoreAlpha;
|
|---|
| 298 | end;
|
|---|
| 299 |
|
|---|
| 300 | function TDitheringToIndexedImage.ComputeMinimumScanlineSize(
|
|---|
| 301 | AWidthInPixels: integer): PtrInt;
|
|---|
| 302 | begin
|
|---|
| 303 | result := (AWidthInPixels*FBitsPerPixel+7) shr 3;
|
|---|
| 304 | end;
|
|---|
| 305 |
|
|---|
| 306 | function TDitheringToIndexedImage.AllocateSpaceForIndexedData(AImage: TBGRACustomBitmap;
|
|---|
| 307 | AScanlineSize: PtrInt): pointer;
|
|---|
| 308 | var size: integer;
|
|---|
| 309 | begin
|
|---|
| 310 | size := AScanlineSize * AImage.Height;
|
|---|
| 311 | GetMem(result, size);
|
|---|
| 312 | Fillchar(result^, size, 0);
|
|---|
| 313 | end;
|
|---|
| 314 |
|
|---|
| 315 | function TDitheringToIndexedImage.DitherImage(AAlgorithm: TDitheringAlgorithm;
|
|---|
| 316 | AImage: TBGRACustomBitmap): Pointer;
|
|---|
| 317 | begin
|
|---|
| 318 | result := DitherImage(AAlgorithm, AImage, ComputeMinimumScanlineSize(AImage.Width));
|
|---|
| 319 | end;
|
|---|
| 320 |
|
|---|
| 321 | procedure TDitheringToIndexedImage.DitherImageTo(AAlgorithm: TDitheringAlgorithm;
|
|---|
| 322 | AImage: TBGRACustomBitmap; AData: Pointer);
|
|---|
| 323 | begin
|
|---|
| 324 | DitherImageTo(AAlgorithm, AImage, AData, ComputeMinimumScanlineSize(AImage.Width));
|
|---|
| 325 | end;
|
|---|
| 326 |
|
|---|
| 327 | function TDitheringToIndexedImage.DitherImage(AAlgorithm: TDitheringAlgorithm;
|
|---|
| 328 | AImage: TBGRACustomBitmap; AScanlineSize: PtrInt): Pointer;
|
|---|
| 329 | begin
|
|---|
| 330 | result := AllocateSpaceForIndexedData(AImage, AScanlineSize);
|
|---|
| 331 | DitherImageTo(AAlgorithm, AImage, result, AScanlineSize);
|
|---|
| 332 | end;
|
|---|
| 333 |
|
|---|
| 334 | procedure TDitheringToIndexedImage.DitherImageTo(AAlgorithm: TDitheringAlgorithm;
|
|---|
| 335 | AImage: TBGRACustomBitmap; AData: Pointer; AScanlineSize: PtrInt);
|
|---|
| 336 | var ditherTask: TDitheringTask;
|
|---|
| 337 | begin
|
|---|
| 338 | FCurrentOutputY := -1;
|
|---|
| 339 | FCurrentOutputScanline := nil;
|
|---|
| 340 | FCurrentData := AData;
|
|---|
| 341 | FCurrentMaxY:= AImage.Height-1;
|
|---|
| 342 | FCurrentScanlineSize:= AScanlineSize;
|
|---|
| 343 |
|
|---|
| 344 | ditherTask := CreateDitheringTask(AAlgorithm, AImage, FPalette, FIgnoreAlpha);
|
|---|
| 345 | try
|
|---|
| 346 | ditherTask.Inplace := True; //do not allocate destination
|
|---|
| 347 | if BitsPerPixel >= 8 then
|
|---|
| 348 | ditherTask.OnOutputPixel := @OutputPixelFullByte
|
|---|
| 349 | else
|
|---|
| 350 | begin
|
|---|
| 351 | ditherTask.OnOutputPixel:= @OutputPixelSubByte;
|
|---|
| 352 | if BitOrder = riboBitsInOrder then
|
|---|
| 353 | FCurrentBitOrderMask := 0
|
|---|
| 354 | else
|
|---|
| 355 | FCurrentBitOrderMask := $ff;
|
|---|
| 356 | end;
|
|---|
| 357 | ditherTask.Execute;
|
|---|
| 358 | finally
|
|---|
| 359 | ditherTask.Free;
|
|---|
| 360 | end;
|
|---|
| 361 | end;
|
|---|
| 362 |
|
|---|
| 363 | { TDitheringTask }
|
|---|
| 364 |
|
|---|
| 365 | procedure TDitheringTask.OutputPixel(X, Y: NativeInt; AColorIndex: NativeInt;
|
|---|
| 366 | AColor: TBGRAPixel);
|
|---|
| 367 | begin
|
|---|
| 368 | if Y <> FCurrentOutputY then
|
|---|
| 369 | begin
|
|---|
| 370 | FCurrentOutputY := Y;
|
|---|
| 371 | FCurrentOutputScanline := Destination.ScanLine[y];
|
|---|
| 372 | end;
|
|---|
| 373 | PutPixels(FCurrentOutputScanline+x, @AColor, 1, FDrawMode, 255);
|
|---|
| 374 | end;
|
|---|
| 375 |
|
|---|
| 376 | procedure TDitheringTask.ApproximateColor(const AColor: TBGRAPixel;
|
|---|
| 377 | out AApproxColor: TBGRAPixel; out AIndex: integer);
|
|---|
| 378 | begin
|
|---|
| 379 | if FPalette <> nil then
|
|---|
| 380 | begin
|
|---|
| 381 | AIndex := FPalette.FindNearestColorIndex(AColor, FIgnoreAlpha);
|
|---|
| 382 | if AIndex = -1 then
|
|---|
| 383 | AApproxColor := BGRAPixelTransparent
|
|---|
| 384 | else
|
|---|
| 385 | AApproxColor := FPalette.Color[AIndex];
|
|---|
| 386 | end else
|
|---|
| 387 | begin
|
|---|
| 388 | if AColor.alpha = 0 then
|
|---|
| 389 | begin
|
|---|
| 390 | AApproxColor := BGRAPixelTransparent;
|
|---|
| 391 | AIndex := -1;
|
|---|
| 392 | end else
|
|---|
| 393 | begin
|
|---|
| 394 | AApproxColor := AColor;
|
|---|
| 395 | AIndex := 0;
|
|---|
| 396 | end;
|
|---|
| 397 | end;
|
|---|
| 398 | end;
|
|---|
| 399 |
|
|---|
| 400 | constructor TDitheringTask.Create(ASource: IBGRAScanner;
|
|---|
| 401 | APalette: TBGRACustomApproxPalette; ADestination: TBGRACustomBitmap;
|
|---|
| 402 | AIgnoreAlpha: boolean; ABounds: TRect);
|
|---|
| 403 | begin
|
|---|
| 404 | FPalette := APalette;
|
|---|
| 405 | SetSource(ASource);
|
|---|
| 406 | FBounds := ABounds;
|
|---|
| 407 | FIgnoreAlpha:= AIgnoreAlpha;
|
|---|
| 408 | FCurrentOutputY := -1;
|
|---|
| 409 | FCurrentOutputScanline:= nil;
|
|---|
| 410 | OnOutputPixel:= @OutputPixel;
|
|---|
| 411 | Destination := ADestination;
|
|---|
| 412 | FDrawMode:= dmSet;
|
|---|
| 413 | end;
|
|---|
| 414 |
|
|---|
| 415 | constructor TDitheringTask.Create(bmp: TBGRACustomBitmap;
|
|---|
| 416 | APalette: TBGRACustomApproxPalette; AInPlace: boolean; AIgnoreAlpha: boolean;
|
|---|
| 417 | ABounds: TRect);
|
|---|
| 418 | begin
|
|---|
| 419 | FPalette := APalette;
|
|---|
| 420 | SetSource(bmp);
|
|---|
| 421 | FBounds := ABounds;
|
|---|
| 422 | FIgnoreAlpha:= AIgnoreAlpha;
|
|---|
| 423 | FCurrentOutputY := -1;
|
|---|
| 424 | FCurrentOutputScanline:= nil;
|
|---|
| 425 | OnOutputPixel:= @OutputPixel;
|
|---|
| 426 | InPlace := AInPlace;
|
|---|
| 427 | FDrawMode:= dmSet;
|
|---|
| 428 | end;
|
|---|
| 429 |
|
|---|
| 430 | constructor TDitheringTask.Create(bmp: TBGRACustomBitmap;
|
|---|
| 431 | APalette: TBGRACustomApproxPalette; AInPlace: boolean; AIgnoreAlpha: boolean);
|
|---|
| 432 | begin
|
|---|
| 433 | FPalette := APalette;
|
|---|
| 434 | SetSource(bmp);
|
|---|
| 435 | FBounds := rect(0,0,bmp.Width,bmp.Height);
|
|---|
| 436 | FIgnoreAlpha:= AIgnoreAlpha;
|
|---|
| 437 | FCurrentOutputY := -1;
|
|---|
| 438 | FCurrentOutputScanline:= nil;
|
|---|
| 439 | OnOutputPixel:= @OutputPixel;
|
|---|
| 440 | InPlace := AInPlace;
|
|---|
| 441 | FDrawMode:= dmSet;
|
|---|
| 442 | end;
|
|---|
| 443 |
|
|---|
| 444 | { TFloydSteinbergDitheringTask }
|
|---|
| 445 |
|
|---|
| 446 | procedure TFloydSteinbergDitheringTask.DoExecute;
|
|---|
| 447 | const
|
|---|
| 448 | ErrorPrecisionShift = 4;
|
|---|
| 449 | MaxColorDiffForDiffusion = 4096;
|
|---|
| 450 | type
|
|---|
| 451 | TAccPixel = record
|
|---|
| 452 | red,green,blue,alpha: NativeInt;
|
|---|
| 453 | end;
|
|---|
| 454 | TLine = array of TAccPixel;
|
|---|
| 455 |
|
|---|
| 456 | procedure AddError(var dest: TAccPixel; const src: TAccPixel; factor: NativeInt);
|
|---|
| 457 | const maxError = 65536 shl ErrorPrecisionShift;
|
|---|
| 458 | minError = -(65536 shl ErrorPrecisionShift);
|
|---|
| 459 | begin
|
|---|
| 460 | dest.alpha += src.alpha * factor;
|
|---|
| 461 | if dest.alpha > maxError then dest.alpha := maxError;
|
|---|
| 462 | if dest.alpha < minError then dest.alpha := minError;
|
|---|
| 463 | dest.red += src.red * factor;
|
|---|
| 464 | if dest.red > maxError then dest.red := maxError;
|
|---|
| 465 | if dest.red < minError then dest.red := minError;
|
|---|
| 466 | dest.green += src.green * factor;
|
|---|
| 467 | if dest.green > maxError then dest.green := maxError;
|
|---|
| 468 | if dest.green < minError then dest.green := minError;
|
|---|
| 469 | dest.blue += src.blue * factor;
|
|---|
| 470 | if dest.blue > maxError then dest.blue := maxError;
|
|---|
| 471 | if dest.blue < minError then dest.blue := minError;
|
|---|
| 472 | end;
|
|---|
| 473 |
|
|---|
| 474 | var
|
|---|
| 475 | w,h: NativeInt;
|
|---|
| 476 |
|
|---|
| 477 | var
|
|---|
| 478 | p,pNext: PExpandedPixel;
|
|---|
| 479 | destX,destY: NativeInt;
|
|---|
| 480 | orig,cur,approxExp: TExpandedPixel;
|
|---|
| 481 | approx: TBGRAPixel;
|
|---|
| 482 | approxIndex: integer;
|
|---|
| 483 | curPix,diff: TAccPixel;
|
|---|
| 484 | i: NativeInt;
|
|---|
| 485 | yWrite: NativeInt;
|
|---|
| 486 | tempLine, currentLine, nextLine: TLine;
|
|---|
| 487 |
|
|---|
| 488 | nextScan,curScan: PExpandedPixel;
|
|---|
| 489 |
|
|---|
| 490 | function ClampWordDiv(AValue: NativeInt): Word; inline;
|
|---|
| 491 | begin
|
|---|
| 492 | if AValue < 0 then AValue := -((-AValue) shr ErrorPrecisionShift) else AValue := AValue shr ErrorPrecisionShift;
|
|---|
| 493 | if AValue < 0 then
|
|---|
| 494 | result := 0
|
|---|
| 495 | else if AValue > 65535 then
|
|---|
| 496 | result := 65535
|
|---|
| 497 | else
|
|---|
| 498 | result := AValue;
|
|---|
| 499 | end;
|
|---|
| 500 |
|
|---|
| 501 | function Div16(AValue: NativeInt): NativeInt; inline;
|
|---|
| 502 | begin
|
|---|
| 503 | if AValue < 0 then
|
|---|
| 504 | result := -((-AValue) shr 4)
|
|---|
| 505 | else
|
|---|
| 506 | result := AValue shr 4;
|
|---|
| 507 | end;
|
|---|
| 508 |
|
|---|
| 509 | begin
|
|---|
| 510 | w := FBounds.Right-FBounds.Left;
|
|---|
| 511 | h := FBounds.Bottom-FBounds.Top;
|
|---|
| 512 | if (w <= 0) or (h <= 0) then exit;
|
|---|
| 513 | setlength(currentLine,w);
|
|---|
| 514 | setlength(nextLine,w);
|
|---|
| 515 | curScan := nil;
|
|---|
| 516 | nextScan := RequestSourceExpandedScanLine(FBounds.Left, FBounds.Top, FBounds.Right-FBounds.Left);
|
|---|
| 517 | for yWrite := 0 to h-1 do
|
|---|
| 518 | begin
|
|---|
| 519 | if GetShouldStop(yWrite) then break;
|
|---|
| 520 | ReleaseSourceExpandedScanLine(curScan);
|
|---|
| 521 | curScan := nextScan;
|
|---|
| 522 | nextScan := nil;
|
|---|
| 523 | p := curScan;
|
|---|
| 524 | destX := FBounds.Left;
|
|---|
| 525 | destY := yWrite+FBounds.Top;
|
|---|
| 526 | if yWrite < h-1 then
|
|---|
| 527 | nextScan := RequestSourceExpandedScanLine(FBounds.Left,yWrite+FBounds.Top+1, FBounds.Right-FBounds.Left);
|
|---|
| 528 | pNext := nextScan;
|
|---|
| 529 | if odd(yWrite) then
|
|---|
| 530 | begin
|
|---|
| 531 | inc(p, w);
|
|---|
| 532 | inc(destX, w);
|
|---|
| 533 | if pNext<>nil then inc(pNext, w);
|
|---|
| 534 | for i := w-1 downto 0 do
|
|---|
| 535 | begin
|
|---|
| 536 | dec(p);
|
|---|
| 537 | dec(destX);
|
|---|
| 538 | if pNext<>nil then dec(pNext);
|
|---|
| 539 | if p^.alpha <> 0 then
|
|---|
| 540 | begin
|
|---|
| 541 | orig := p^;
|
|---|
| 542 | with currentLine[i] do
|
|---|
| 543 | begin
|
|---|
| 544 | curPix.alpha := alpha+NativeInt(orig.alpha shl ErrorPrecisionShift);
|
|---|
| 545 | curPix.red := red+NativeInt(orig.red shl ErrorPrecisionShift);
|
|---|
| 546 | curPix.green := green+NativeInt(orig.green shl ErrorPrecisionShift);
|
|---|
| 547 | curPix.blue := blue+NativeInt(orig.blue shl ErrorPrecisionShift);
|
|---|
| 548 | cur.alpha := ClampWordDiv(curPix.alpha);
|
|---|
| 549 | cur.red := ClampWordDiv(curPix.red);
|
|---|
| 550 | cur.green := ClampWordDiv(curPix.green);
|
|---|
| 551 | cur.blue := ClampWordDiv(curPix.blue);
|
|---|
| 552 | end;
|
|---|
| 553 | ApproximateColor(GammaCompression(cur), approx, approxIndex);
|
|---|
| 554 | approxExp := GammaExpansion(approx);
|
|---|
| 555 | diff.alpha := Div16(curPix.alpha - (approxExp.alpha shl ErrorPrecisionShift));
|
|---|
| 556 | if (approxExp.alpha = 0) or (cur.alpha = 0) then
|
|---|
| 557 | begin
|
|---|
| 558 | diff.red := 0;
|
|---|
| 559 | diff.green := 0;
|
|---|
| 560 | diff.blue := 0;
|
|---|
| 561 | end else
|
|---|
| 562 | begin
|
|---|
| 563 | diff.red := Div16(curPix.red - (approxExp.red shl ErrorPrecisionShift));
|
|---|
| 564 | diff.green := Div16(curPix.green - (approxExp.green shl ErrorPrecisionShift));
|
|---|
| 565 | diff.blue := Div16(curPix.blue - (approxExp.blue shl ErrorPrecisionShift));
|
|---|
| 566 | end;
|
|---|
| 567 | if i > 0 then
|
|---|
| 568 | begin
|
|---|
| 569 | if AbsRGBADiff((p-1)^,orig) < MaxColorDiffForDiffusion then
|
|---|
| 570 | AddError(currentLine[i-1], diff, 7);
|
|---|
| 571 | end;
|
|---|
| 572 | if nextLine <> nil then
|
|---|
| 573 | begin
|
|---|
| 574 | if i > 0 then
|
|---|
| 575 | begin
|
|---|
| 576 | if AbsRGBADiff((pNext-1)^,orig) < MaxColorDiffForDiffusion then
|
|---|
| 577 | AddError(nextLine[i-1], diff, 1);
|
|---|
| 578 | end;
|
|---|
| 579 | if AbsRGBADiff(pNext^,orig) < MaxColorDiffForDiffusion then
|
|---|
| 580 | AddError(nextLine[i], diff, 5);
|
|---|
| 581 | if i < w-1 then
|
|---|
| 582 | begin
|
|---|
| 583 | if AbsRGBADiff((pNext+1)^,orig) < MaxColorDiffForDiffusion then
|
|---|
| 584 | AddError(nextLine[i+1], diff, 3);
|
|---|
| 585 | end;
|
|---|
| 586 | end;
|
|---|
| 587 | OnOutputPixel(destX,destY,approxIndex,approx);
|
|---|
| 588 | end;
|
|---|
| 589 | end
|
|---|
| 590 | end
|
|---|
| 591 | else
|
|---|
| 592 | for i := 0 to w-1 do
|
|---|
| 593 | begin
|
|---|
| 594 | if p^.alpha <> 0 then
|
|---|
| 595 | begin
|
|---|
| 596 | orig := p^;
|
|---|
| 597 | with currentLine[i] do
|
|---|
| 598 | begin
|
|---|
| 599 | curPix.alpha := alpha+NativeInt(orig.alpha shl ErrorPrecisionShift);
|
|---|
| 600 | curPix.red := red+NativeInt(orig.red shl ErrorPrecisionShift);
|
|---|
| 601 | curPix.green := green+NativeInt(orig.green shl ErrorPrecisionShift);
|
|---|
| 602 | curPix.blue := blue+NativeInt(orig.blue shl ErrorPrecisionShift);
|
|---|
| 603 | cur.alpha := ClampWordDiv(curPix.alpha);
|
|---|
| 604 | cur.red := ClampWordDiv(curPix.red);
|
|---|
| 605 | cur.green := ClampWordDiv(curPix.green);
|
|---|
| 606 | cur.blue := ClampWordDiv(curPix.blue);
|
|---|
| 607 | end;
|
|---|
| 608 | ApproximateColor(GammaCompression(cur), approx, approxIndex);
|
|---|
| 609 | approxExp := GammaExpansion(approx);
|
|---|
| 610 | diff.alpha := Div16(curPix.alpha - (approxExp.alpha shl ErrorPrecisionShift));
|
|---|
| 611 | if (approxExp.alpha = 0) or (cur.alpha = 0) then
|
|---|
| 612 | begin
|
|---|
| 613 | diff.red := 0;
|
|---|
| 614 | diff.green := 0;
|
|---|
| 615 | diff.blue := 0;
|
|---|
| 616 | end else
|
|---|
| 617 | begin
|
|---|
| 618 | diff.red := Div16(curPix.red - (approxExp.red shl ErrorPrecisionShift));
|
|---|
| 619 | diff.green := Div16(curPix.green - (approxExp.green shl ErrorPrecisionShift));
|
|---|
| 620 | diff.blue := Div16(curPix.blue - (approxExp.blue shl ErrorPrecisionShift));
|
|---|
| 621 | end;
|
|---|
| 622 | if i < w-1 then
|
|---|
| 623 | begin
|
|---|
| 624 | if AbsRGBADiff((p+1)^,orig) < MaxColorDiffForDiffusion then
|
|---|
| 625 | AddError(currentLine[i+1], diff, 7);
|
|---|
| 626 | end;
|
|---|
| 627 | if pNext <> nil then
|
|---|
| 628 | begin
|
|---|
| 629 | if i > 0 then
|
|---|
| 630 | begin
|
|---|
| 631 | if AbsRGBADiff((pNext-1)^,orig) < MaxColorDiffForDiffusion then
|
|---|
| 632 | AddError(nextLine[i-1], diff, 3);
|
|---|
| 633 | end;
|
|---|
| 634 | if AbsRGBADiff(pNext^,orig) < MaxColorDiffForDiffusion then
|
|---|
| 635 | AddError(nextLine[i], diff, 5);
|
|---|
| 636 | if i < w-1 then
|
|---|
| 637 | begin
|
|---|
| 638 | if AbsRGBADiff((pNext+1)^,orig) < MaxColorDiffForDiffusion then
|
|---|
| 639 | AddError(nextLine[i+1], diff, 1);
|
|---|
| 640 | end;
|
|---|
| 641 | end;
|
|---|
| 642 | OnOutputPixel(destX,destY,approxIndex,approx);
|
|---|
| 643 | end;
|
|---|
| 644 | inc(p);
|
|---|
| 645 | inc(destX);
|
|---|
| 646 | if pNext<>nil then inc(pNext);
|
|---|
| 647 | end;
|
|---|
| 648 | tempLine := currentLine;
|
|---|
| 649 | currentLine := nextLine;
|
|---|
| 650 | nextLine := tempLine;
|
|---|
| 651 | if yWrite = h-2 then
|
|---|
| 652 | nextLine := nil
|
|---|
| 653 | else
|
|---|
| 654 | for i := 0 to w-1 do
|
|---|
| 655 | begin
|
|---|
| 656 | nextLine[i].red := 0;
|
|---|
| 657 | nextLine[i].green := 0;
|
|---|
| 658 | nextLine[i].blue := 0;
|
|---|
| 659 | nextLine[i].alpha := 0;
|
|---|
| 660 | end;
|
|---|
| 661 | end;
|
|---|
| 662 | ReleaseSourceExpandedScanLine(curScan);
|
|---|
| 663 | ReleaseSourceExpandedScanLine(nextScan);
|
|---|
| 664 | Destination.InvalidateBitmap;
|
|---|
| 665 | end;
|
|---|
| 666 |
|
|---|
| 667 | { TNearestColorTask }
|
|---|
| 668 |
|
|---|
| 669 | procedure TNearestColorTask.DoExecute;
|
|---|
| 670 | var yb,xb: NativeInt;
|
|---|
| 671 | curScan,psrc: PBGRAPixel;
|
|---|
| 672 | colorIndex: LongInt;
|
|---|
| 673 | colorValue: TBGRAPixel;
|
|---|
| 674 | begin
|
|---|
| 675 | for yb := FBounds.Top to FBounds.Bottom - 1 do
|
|---|
| 676 | begin
|
|---|
| 677 | if GetShouldStop(yb) then break;
|
|---|
| 678 | curScan := RequestSourceScanLine(FBounds.Left,yb,FBounds.Right-FBounds.Left);
|
|---|
| 679 | psrc := curScan;
|
|---|
| 680 | for xb := FBounds.Left to FBounds.Right-1 do
|
|---|
| 681 | begin
|
|---|
| 682 | ApproximateColor(psrc^, colorValue, colorIndex);
|
|---|
| 683 | OnOutputPixel(xb,yb,colorIndex,colorValue);
|
|---|
| 684 | inc(psrc);
|
|---|
| 685 | end;
|
|---|
| 686 | ReleaseSourceScanLine(curScan);
|
|---|
| 687 | end;
|
|---|
| 688 | Destination.InvalidateBitmap;
|
|---|
| 689 | end;
|
|---|
| 690 |
|
|---|
| 691 | end.
|
|---|
| 692 |
|
|---|