PngFunctions.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. unit PngFunctions;
  2. interface
  3. uses
  4. Windows, Graphics, ImgList, Contnrs, pngimage;
  5. {$IF RTLVersion < 20.0 }
  6. {$IF RTLVersion < 15.0 }
  7. PngComponents are only compatible with Delphi 7 and higher!
  8. {$IFEND}
  9. type
  10. TPngImage = TPNGObject;
  11. {$IFEND}
  12. type
  13. TPngOption = (pngBlendOnDisabled, pngGrayscaleOnDisabled);
  14. TPngOptions = set of TPngOption;
  15. procedure MakeDisabledImage(Image: TPngImage; const Options: TPngOptions);
  16. procedure MakeImageBlended(Image: TPngImage; Amount: Byte = 127);
  17. procedure MakeImageGrayscale(Image: TPngImage; Amount: Byte = 255);
  18. procedure DrawPNG(Png: TPngImage; Canvas: TCanvas; const ARect: TRect; const Options: TPngOptions);
  19. procedure ConvertToPNG(Source: TGraphic; Dest: TPngImage);
  20. procedure CreatePNG(Color, Mask: TBitmap; Dest: TPngImage; InverseMask: Boolean = False);
  21. procedure CreatePNGMasked(Bitmap: TBitmap; Mask: TColor; Dest: TPngImage);
  22. procedure SlicePNG(JoinedPNG: TPngImage; Columns, Rows: Integer; out SlicedPNGs: TObjectList);
  23. implementation
  24. uses
  25. SysUtils;
  26. function ColorToTriple(Color: TColor): TRGBTriple;
  27. var
  28. ColorRGB: Longint;
  29. begin
  30. ColorRGB := ColorToRGB(Color);
  31. Result.rgbtBlue := ColorRGB shr 16 and $FF;
  32. Result.rgbtGreen := ColorRGB shr 8 and $FF;
  33. Result.rgbtRed := ColorRGB and $FF;
  34. end;
  35. procedure MakeImageBlended(Image: TPngImage; Amount: Byte = 127);
  36. procedure ForceAlphachannel(BitTransparency: Boolean; TransparentColor: TColor);
  37. var
  38. Assigner: TBitmap;
  39. Temp: TPngImage;
  40. X, Y: Integer;
  41. Line: pngimage.PByteArray;
  42. Current: TColor;
  43. begin
  44. //Not all formats of PNG support an alpha-channel (paletted images for example),
  45. //so with this function, I simply recreate the PNG as being 32-bits, effectivly
  46. //forcing an alpha-channel on it.
  47. Temp := TPngImage.Create;
  48. try
  49. Assigner := TBitmap.Create;
  50. try
  51. Assigner.Width := Image.Width;
  52. Assigner.Height := Image.Height;
  53. Temp.Assign(Assigner);
  54. finally
  55. Assigner.Free;
  56. end;
  57. Temp.CreateAlpha;
  58. for Y := 0 to Image.Height - 1 do begin
  59. Line := Temp.AlphaScanline[Y];
  60. for X := 0 to Image.Width - 1 do begin
  61. Current := Image.Pixels[X, Y];
  62. Temp.Pixels[X, Y] := Current;
  63. if BitTransparency and (Current = TransparentColor) then
  64. Line[X] := 0
  65. else
  66. Line[X] := Amount;
  67. end;
  68. end;
  69. Image.Assign(Temp);
  70. finally
  71. Temp.Free;
  72. end;
  73. end;
  74. var
  75. X, Y: Integer;
  76. Line: pngimage.PByteArray;
  77. Forced: Boolean;
  78. TransparentColor: TColor;
  79. BitTransparency: Boolean;
  80. begin
  81. //If the PNG doesn't have an alpha channel, then add one
  82. BitTransparency := Image.TransparencyMode = ptmBit;
  83. TransparentColor := Image.TransparentColor;
  84. Forced := False;
  85. if not (Image.Header.ColorType in [COLOR_RGBALPHA, COLOR_GRAYSCALEALPHA]) then begin
  86. Forced := Image.Header.ColorType in [COLOR_GRAYSCALE, COLOR_PALETTE];
  87. if Forced then
  88. ForceAlphachannel(BitTransparency, TransparentColor)
  89. else
  90. Image.CreateAlpha;
  91. end;
  92. //Divide the alpha values by 2
  93. if not Forced and (Image.Header.ColorType in [COLOR_RGBALPHA, COLOR_GRAYSCALEALPHA]) then begin
  94. for Y := 0 to Image.Height - 1 do begin
  95. Line := Image.AlphaScanline[Y];
  96. for X := 0 to Image.Width - 1 do begin
  97. if BitTransparency and (Image.Pixels[X, Y] = TransparentColor) then
  98. Line[X] := 0
  99. else
  100. Line[X] := Round(Line[X] / 256 * (Amount + 1));
  101. end;
  102. end;
  103. end;
  104. end;
  105. procedure MakeImageGrayscale(Image: TPngImage; Amount: Byte = 255);
  106. procedure GrayscaleRGB(var R, G, B: Byte);
  107. { Performance optimized version without floating point operations by Christian Budde }
  108. var
  109. X: Byte;
  110. begin
  111. X := (R * 77 + G * 150 + B * 29) shr 8;
  112. R := ((R * (255 - Amount)) + (X * Amount) + 128) shr 8;
  113. G := ((G * (255 - Amount)) + (X * Amount) + 128) shr 8;
  114. B := ((B * (255 - Amount)) + (X * Amount) + 128) shr 8;
  115. (* original code
  116. X := Round(R * 0.30 + G * 0.59 + B * 0.11);
  117. R := Round(R / 256 * (256 - Amount - 1)) + Round(X / 256 * (Amount + 1));
  118. G := Round(G / 256 * (256 - Amount - 1)) + Round(X / 256 * (Amount + 1));
  119. B := Round(B / 256 * (256 - Amount - 1)) + Round(X / 256 * (Amount + 1));
  120. *)
  121. end;
  122. var
  123. X, Y, PalCount: Integer;
  124. Line: PRGBLine;
  125. PaletteHandle: HPalette;
  126. Palette: array[Byte] of TPaletteEntry;
  127. begin
  128. //Don't do anything if the image is already a grayscaled one
  129. if not (Image.Header.ColorType in [COLOR_GRAYSCALE, COLOR_GRAYSCALEALPHA]) then begin
  130. if Image.Header.ColorType = COLOR_PALETTE then begin
  131. //Grayscale every palette entry
  132. PaletteHandle := Image.Palette;
  133. PalCount := GetPaletteEntries(PaletteHandle, 0, 256, Palette);
  134. for X := 0 to PalCount - 1 do
  135. GrayscaleRGB(Palette[X].peRed, Palette[X].peGreen, Palette[X].peBlue);
  136. SetPaletteEntries(PaletteHandle, 0, PalCount, Palette);
  137. Image.Palette := PaletteHandle;
  138. end
  139. else begin
  140. //Grayscale every pixel
  141. for Y := 0 to Image.Height - 1 do begin
  142. Line := Image.Scanline[Y];
  143. for X := 0 to Image.Width - 1 do
  144. GrayscaleRGB(Line[X].rgbtRed, Line[X].rgbtGreen, Line[X].rgbtBlue);
  145. end;
  146. end;
  147. end;
  148. end;
  149. procedure DrawPNG(Png: TPngImage; Canvas: TCanvas; const ARect: TRect; const Options: TPngOptions);
  150. var
  151. PngCopy: TPngImage;
  152. begin
  153. if Options <> [] then begin
  154. PngCopy := TPngImage.Create;
  155. try
  156. PngCopy.Assign(Png);
  157. if pngBlendOnDisabled in Options then
  158. MakeImageBlended(PngCopy);
  159. if pngGrayscaleOnDisabled in Options then
  160. MakeImageGrayscale(PngCopy);
  161. PngCopy.Draw(Canvas, ARect);
  162. finally
  163. PngCopy.Free;
  164. end;
  165. end
  166. else begin
  167. Png.Draw(Canvas, ARect);
  168. end;
  169. end;
  170. procedure ConvertToPNG(Source: TGraphic; Dest: TPngImage);
  171. type
  172. TRGBALine = array[Word] of TRGBQuad;
  173. PRGBALine = ^TRGBALine;
  174. var
  175. MaskLines: array of pngimage.PByteArray;
  176. function ColorToTriple(const Color: TColor): TRGBTriple;
  177. begin
  178. Result.rgbtBlue := Color shr 16 and $FF;
  179. Result.rgbtGreen := Color shr 8 and $FF;
  180. Result.rgbtRed := Color and $FF;
  181. end;
  182. procedure GetAlphaMask(SourceColor: TBitmap);
  183. type
  184. TBitmapInfoV4 = packed record
  185. bmiHeader: TBitmapV4Header; //Otherwise I may not get per-pixel alpha values.
  186. bmiColors: array[0..2] of TRGBQuad; // reserve space for color lookup table
  187. end;
  188. var
  189. Bits: PRGBALine;
  190. { The BitmapInfo parameter to GetDIBits is delared as var parameter. So instead of casting around, we simply use
  191. the absolute directive to refer to the same memory area. }
  192. BitmapInfo: TBitmapInfoV4;
  193. BitmapInfoFake: TBitmapInfo absolute BitmapInfo;
  194. I, X, Y: Integer;
  195. HasAlpha: Boolean;
  196. BitsSize: Integer;
  197. bmpDC: HDC;
  198. bmpHandle: HBITMAP;
  199. begin
  200. BitsSize := 4 * SourceColor.Width * SourceColor.Height;
  201. Bits := AllocMem(BitsSize);
  202. try
  203. FillChar(BitmapInfo, SizeOf(BitmapInfo), 0);
  204. BitmapInfo.bmiHeader.bV4Size := SizeOf(BitmapInfo.bmiHeader);
  205. BitmapInfo.bmiHeader.bV4Width := SourceColor.Width;
  206. BitmapInfo.bmiHeader.bV4Height := -SourceColor.Height; //Otherwise the image is upside down.
  207. BitmapInfo.bmiHeader.bV4Planes := 1;
  208. BitmapInfo.bmiHeader.bV4BitCount := 32;
  209. BitmapInfo.bmiHeader.bV4V4Compression := BI_BITFIELDS;
  210. BitmapInfo.bmiHeader.bV4SizeImage := BitsSize;
  211. BitmapInfo.bmiColors[0].rgbRed := 255;
  212. BitmapInfo.bmiColors[1].rgbGreen := 255;
  213. BitmapInfo.bmiColors[2].rgbBlue := 255;
  214. { Getting the bitmap Handle will invalidate the Canvas.Handle, so it is important to retrieve them in the correct
  215. order. As parameter evaluation order is undefined and differs between Win32 and Win64, we get invalid values
  216. for Canvas.Handle when we use those properties directly in the call to GetDIBits. }
  217. bmpHandle := SourceColor.Handle;
  218. bmpDC := SourceColor.Canvas.Handle;
  219. if GetDIBits(bmpDC, bmpHandle, 0, SourceColor.Height, Bits, BitmapInfoFake, DIB_RGB_COLORS) > 0 then begin
  220. //Because Win32 API is a piece of crap when it comes to icons, I have to check
  221. //whether an has an alpha-channel the hard way.
  222. HasAlpha := False;
  223. for I := 0 to (SourceColor.Height * SourceColor.Width) - 1 do begin
  224. if Bits[I].rgbReserved <> 0 then begin
  225. HasAlpha := True;
  226. Break;
  227. end;
  228. end;
  229. if HasAlpha then begin
  230. //OK, so not all alpha-values are 0, which indicates the existence of an
  231. //alpha-channel.
  232. I := 0;
  233. for Y := 0 to SourceColor.Height - 1 do
  234. for X := 0 to SourceColor.Width - 1 do begin
  235. MaskLines[Y][X] := Bits[I].rgbReserved;
  236. Inc(I);
  237. end;
  238. end;
  239. end;
  240. finally
  241. FreeMem(Bits, BitsSize);
  242. end;
  243. end;
  244. function WinXPOrHigher: Boolean;
  245. var
  246. Info: TOSVersionInfo;
  247. begin
  248. Info.dwOSVersionInfoSize := SizeOf(Info);
  249. GetVersionEx(Info);
  250. Result := (Info.dwPlatformId = VER_PLATFORM_WIN32_NT) and
  251. ((Info.dwMajorVersion > 5) or
  252. ((Info.dwMajorVersion = 5) and (Info.dwMinorVersion >= 1)));
  253. end;
  254. var
  255. Temp, SourceColor, SourceMask: TBitmap;
  256. X, Y: Integer;
  257. Line: PRGBLine;
  258. MaskLine, AlphaLine: pngimage.PByteArray;
  259. TransparentColor, CurrentColor: TColor;
  260. IconInfo: TIconInfo;
  261. AlphaNeeded: Boolean;
  262. begin
  263. Assert(Dest <> nil, 'Dest is nil!');
  264. //A PNG does not have to be converted
  265. if Source is TPngImage then begin
  266. Dest.Assign(Source);
  267. Exit;
  268. end;
  269. AlphaNeeded := False;
  270. Temp := TBitmap.Create;
  271. SetLength(MaskLines, Source.Height);
  272. for Y := 0 to Source.Height - 1 do begin
  273. MaskLines[Y] := AllocMem(Source.Width);
  274. FillMemory(MaskLines[Y], Source.Width, 255);
  275. end;
  276. try
  277. //Initialize intermediate color bitmap
  278. Temp.Width := Source.Width;
  279. Temp.Height := Source.Height;
  280. Temp.PixelFormat := pf24bit;
  281. //Now figure out the transparency
  282. if Source is TBitmap then begin
  283. if Source.Transparent then begin
  284. //TBitmap is just about comparing the drawn colors against the TransparentColor
  285. if TBitmap(Source).TransparentMode = tmFixed then
  286. TransparentColor := TBitmap(Source).TransparentColor
  287. else
  288. TransparentColor := TBitmap(Source).Canvas.Pixels[0, Source.Height - 1];
  289. for Y := 0 to Temp.Height - 1 do begin
  290. Line := Temp.ScanLine[Y];
  291. MaskLine := MaskLines[Y];
  292. for X := 0 to Temp.Width - 1 do begin
  293. CurrentColor := GetPixel(TBitmap(Source).Canvas.Handle, X, Y);
  294. if CurrentColor = TransparentColor then begin
  295. MaskLine^[X] := 0;
  296. AlphaNeeded := True;
  297. end;
  298. Line[X] := ColorToTriple(CurrentColor);
  299. end;
  300. end;
  301. end
  302. else begin
  303. Temp.Canvas.Draw(0, 0, Source);
  304. end;
  305. end
  306. else if Source is TIcon then begin
  307. //TIcon is more complicated, because there are bitmasked (classic) icons and
  308. //alphablended (modern) icons. Not to forget about the "inverse" color.
  309. GetIconInfo(TIcon(Source).Handle, IconInfo);
  310. SourceColor := TBitmap.Create;
  311. SourceMask := TBitmap.Create;
  312. try
  313. SourceColor.Handle := IconInfo.hbmColor;
  314. SourceMask.Handle := IconInfo.hbmMask;
  315. Temp.Canvas.Draw(0, 0, SourceColor);
  316. for Y := 0 to Temp.Height - 1 do begin
  317. MaskLine := MaskLines[Y];
  318. for X := 0 to Temp.Width - 1 do begin
  319. if GetPixel(SourceMask.Canvas.Handle, X, Y) <> 0 then begin
  320. MaskLine^[X] := 0;
  321. AlphaNeeded := True;
  322. end;
  323. end;
  324. end;
  325. if (GetDeviceCaps(SourceColor.Canvas.Handle, BITSPIXEL) = 32) and WinXPOrHigher then begin
  326. //This doesn't neccesarily mean we actually have 32bpp in the icon, because the
  327. //bpp of an icon is always the same as the display settings, regardless of the
  328. //actual color depth of the icon :(
  329. AlphaNeeded := True;
  330. GetAlphaMask(SourceColor);
  331. end;
  332. //This still doesn't work for alphablended icons...
  333. finally
  334. SourceColor.Free;
  335. SourceMask.Free
  336. end;
  337. end;
  338. //And finally, assign the destination PNG image
  339. Dest.Assign(Temp);
  340. if AlphaNeeded then begin
  341. Dest.CreateAlpha;
  342. for Y := 0 to Dest.Height - 1 do begin
  343. AlphaLine := Dest.AlphaScanline[Y];
  344. CopyMemory(AlphaLine, MaskLines[Y], Temp.Width);
  345. end;
  346. end;
  347. finally
  348. for Y := 0 to Source.Height - 1 do
  349. FreeMem(MaskLines[Y], Source.Width);
  350. Temp.Free;
  351. end;
  352. end;
  353. procedure CreatePNG(Color, Mask: TBitmap; Dest: TPngImage; InverseMask: Boolean = False);
  354. var
  355. Temp: TBitmap;
  356. Line: pngimage.PByteArray;
  357. X, Y: Integer;
  358. begin
  359. Assert(Dest <> nil, 'Dest is nil!');
  360. //Create a PNG from two separate color and mask bitmaps. InverseMask should be
  361. //True if white means transparent, and black means opaque.
  362. if not (Color.PixelFormat in [pf24bit, pf32bit]) then begin
  363. Temp := TBitmap.Create;
  364. try
  365. Temp.Assign(Color);
  366. Temp.PixelFormat := pf24bit;
  367. Dest.Assign(Temp);
  368. finally
  369. Temp.Free;
  370. end;
  371. end
  372. else begin
  373. Dest.Assign(Color);
  374. end;
  375. //Copy the alpha channel.
  376. Dest.CreateAlpha;
  377. for Y := 0 to Dest.Height - 1 do begin
  378. Line := Dest.AlphaScanline[Y];
  379. for X := 0 to Dest.Width - 1 do begin
  380. if InverseMask then
  381. Line[X] := 255 - (GetPixel(Mask.Canvas.Handle, X, Y) and $FF)
  382. else
  383. Line[X] := GetPixel(Mask.Canvas.Handle, X, Y) and $FF;
  384. end;
  385. end;
  386. end;
  387. procedure CreatePNGMasked(Bitmap: TBitmap; Mask: TColor; Dest: TPngImage);
  388. var
  389. Temp: TBitmap;
  390. Line: pngimage.PByteArray;
  391. X, Y: Integer;
  392. begin
  393. Assert(Dest <> nil, 'Dest is nil!');
  394. //Create a PNG from two separate color and mask bitmaps. InverseMask should be
  395. //True if white means transparent, and black means opaque.
  396. if not (Bitmap.PixelFormat in [pf24bit, pf32bit]) then begin
  397. Temp := TBitmap.Create;
  398. try
  399. Temp.Assign(Bitmap);
  400. Temp.PixelFormat := pf24bit;
  401. Dest.Assign(Temp);
  402. finally
  403. Temp.Free;
  404. end;
  405. end
  406. else begin
  407. Dest.Assign(Bitmap);
  408. end;
  409. //Copy the alpha channel.
  410. Dest.CreateAlpha;
  411. for Y := 0 to Dest.Height - 1 do begin
  412. Line := Dest.AlphaScanline[Y];
  413. for X := 0 to Dest.Width - 1 do
  414. Line[X] := Integer(TColor(GetPixel(Bitmap.Canvas.Handle, X, Y)) <> Mask) * $FF;
  415. end;
  416. end;
  417. procedure SlicePNG(JoinedPNG: TPngImage; Columns, Rows: Integer; out SlicedPNGs: TObjectList);
  418. var
  419. X, Y, ImageX, ImageY, OffsetX, OffsetY: Integer;
  420. Width, Height: Integer;
  421. Bitmap: TBitmap;
  422. BitmapLine: PRGBLine;
  423. AlphaLineA, AlphaLineB: pngimage.PByteArray;
  424. PNG: TPngImage;
  425. begin
  426. //This function slices a large PNG file (e.g. an image with all images for a
  427. //toolbar) into smaller, equally-sized pictures.
  428. SlicedPNGs := TObjectList.Create(False);
  429. Width := JoinedPNG.Width div Columns;
  430. Height := JoinedPNG.Height div Rows;
  431. //Loop through the columns and rows to create each individual image
  432. for ImageY := 0 to Rows - 1 do begin
  433. for ImageX := 0 to Columns - 1 do begin
  434. OffsetX := ImageX * Width;
  435. OffsetY := ImageY * Height;
  436. Bitmap := TBitmap.Create;
  437. try
  438. Bitmap.Width := Width;
  439. Bitmap.Height := Height;
  440. Bitmap.PixelFormat := pf24bit;
  441. //Copy the color information into a temporary bitmap. We can't use TPngImage.Draw
  442. //here, because that would combine the color and alpha values.
  443. for Y := 0 to Bitmap.Height - 1 do begin
  444. BitmapLine := Bitmap.Scanline[Y];
  445. for X := 0 to Bitmap.Width - 1 do
  446. BitmapLine[X] := ColorToTriple(JoinedPNG.Pixels[X + OffsetX, Y + OffsetY]);
  447. end;
  448. PNG := TPngImage.Create;
  449. PNG.Assign(Bitmap);
  450. if JoinedPNG.Header.ColorType in [COLOR_GRAYSCALEALPHA, COLOR_RGBALPHA] then begin
  451. //Copy the alpha channel
  452. PNG.CreateAlpha;
  453. for Y := 0 to PNG.Height - 1 do begin
  454. AlphaLineA := JoinedPNG.AlphaScanline[Y + OffsetY];
  455. AlphaLineB := PNG.AlphaScanline[Y];
  456. for X := 0 to PNG.Width - 1 do
  457. AlphaLineB[X] := AlphaLineA[X + OffsetX];
  458. end;
  459. end;
  460. SlicedPNGs.Add(PNG);
  461. finally
  462. Bitmap.Free;
  463. end;
  464. end;
  465. end;
  466. end;
  467. procedure MakeDisabledImage(Image: TPngImage; const Options: TPngOptions);
  468. begin
  469. if pngBlendOnDisabled in Options then
  470. MakeImageBlended(Image);
  471. if pngGrayscaleOnDisabled in Options then
  472. MakeImageGrayscale(Image);
  473. end;
  474. {$IF RTLVersion >= 20.0 }
  475. {$IFDEF RegisterOldPngFormat}
  476. type
  477. TPNGObject = class(TPngImage);
  478. initialization
  479. TPicture.RegisterFileFormat('', '', TPNGObject);
  480. finalization
  481. TPicture.UnregisterGraphicClass(TPNGObject);
  482. {$IFEND}
  483. {$IFEND}
  484. end.