PngFunctions.pas 17 KB

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