With Delphi 2010 you can get the pixelformat of a jpg file with
TJPEGImage ( Image.Picture.Graphic ).PixelFormat
Is there a way to get the pixelformat or bitdepth of TPNGImage?
I tried this but it returns incorrect bitdepth:
if Lowercase ( ExtractFileExt ( FPath ) ) = '.png' then
StatusBar1.Panels [ 4 ].Text := ' Color Depth: ' + IntToStr( TPNGImage ( Image.Picture.Graphic ).Header.ColorType ) + '-bit';
you must use the BitDepth field
TPNGImage(Image.Picture.Graphic ).Header.BitDepth)
and using the ColorType field you can wirte a function like this
function BitsForPixel(const AColorType, ABitDepth: Byte): Integer;
begin
case AColorType of
COLOR_GRAYSCALEALPHA: Result := (ABitDepth * 2);
COLOR_RGB: Result := (ABitDepth * 3);
COLOR_RGBALPHA: Result := (ABitDepth * 4);
COLOR_GRAYSCALE, COLOR_PALETTE: Result := ABitDepth;
else
Result := 0;
end;
end;
and use like so
procedure TForm72.Button1Click(Sender: TObject);
begin
ShowMessage(IntToStr( BitsForPixel(
TPNGImage ( Image1.Picture.Graphic ).Header.ColorType,
TPNGImage ( Image1.Picture.Graphic ).Header.BitDepth
)));
end;
As Rodrigo pointed out, Header.BitDepth is the value to use. The pitfall is that you have to interpret it depending on the ColorType. You may find some hints in the comments inside function BytesForPixels in PngImage.pas:
{Calculates number of bytes for the number of pixels using the}
{color mode in the paramenter}
function BytesForPixels(const Pixels: Integer; const ColorType,
BitDepth: Byte): Integer;
begin
case ColorType of
{Palette and grayscale contains a single value, for palette}
{an value of size 2^bitdepth pointing to the palette index}
{and grayscale the value from 0 to 2^bitdepth with color intesity}
COLOR_GRAYSCALE, COLOR_PALETTE:
Result := (Pixels * BitDepth + 7) div 8;
{RGB contains 3 values R, G, B with size 2^bitdepth each}
COLOR_RGB:
Result := (Pixels * BitDepth * 3) div 8;
{Contains one value followed by alpha value booth size 2^bitdepth}
COLOR_GRAYSCALEALPHA:
Result := (Pixels * BitDepth * 2) div 8;
{Contains four values size 2^bitdepth, Red, Green, Blue and alpha}
COLOR_RGBALPHA:
Result := (Pixels * BitDepth * 4) div 8;
else
Result := 0;
end {case ColorType}
end;
As you see, for ARGB (= COLOR_RGBALPHA) the BitDepth value is taken for each color part plus the alpha value individually. So BitDepth = 8 will result in a 32-bit value for each pixel.
Related
I use this code to scan very fast Bitmap.
Everything is Ok When PixelFormat = pf24bit but my program only work with 256 image color and for this reason PixelFormat = pf8bit.
When PixelFormat = pf8bit this code show error.
var
w, h: Integer;
CurrRow, OffSet: Integer;
x: byte;
pRed, pGreen, pBlue: PByte;
begin
CurrRow := Integer(aBitmap.Scanline[0]);
OffSet := Integer(aBitmap.Scanline[1]) - CurrRow;
Result:= False;
for h := 0 to aBitmap.Height - 1 do
begin
for w := 0 to aBitmap.Width - 1 do
begin
pBlue := PByte(CurrRow + w * 3);
pGreen := PByte(CurrRow + w * 3 + 1);
pRed := PByte(CurrRow + w * 3 + 2);
end;
inc(CurrRow, OffSet);
end;
now I use this code to swap color but dont work because scanline dont check color
procedure ReplaceColor(aBitmap: TBitmap; swap1, swap2: TRGBQuad);
var
w, h, k: Integer;
pScanline: pByte;
Red, Green, Blue, palindex: Byte;
PalEntries: array[0..255] of TRGBQuad;
begin
if aBitmap.Palette <> 0 then
GetPaletteEntries(aBitmap.Palette, 0, 255, PalEntries);
for h := 0 to aBitmap.Height - 1 do
begin
pScanline := pByte(aBitmap.Scanline[h]);
for w := 0 to aBitmap.Width - 1 do
begin
Blue:= PalEntries[pScanline^].rgbBlue ;
Red:= PalEntries[pScanline^].rgbRed ;
Green:= PalEntries[pScanline^].rgbGreen ;
if (Blue = swap1.rgbBlue) and (Red = swap1.rgbRed) and
(Green = swap1.rgbGreen) then
begin
Blue := swap2.rgbBlue;
Green := swap2.rgbGreen;
Red := swap2.rgbRed;
end
else if (Blue = swap2.rgbBlue) and (Red = swap2.rgbRed) and
(Green = swap2.rgbGreen) then
begin
Blue := swap1.rgbBlue;
Green := swap1.rgbGreen;
Red := swap1.rgbRed;
end;
Inc(pScanline);
end;
end;
end;
The code fails for pf8bit because it is not written to handle pf8bit. It is written to handle pf24bit instead.
The code is expecting each scanline to consist of width number of 3-byte (24 bits) pixels containing the actual RGB values. But in pf8bit, each scanline contains 1-byte (8 bit) pixels which are indexes into the bitmap's color palette. You are not accounting for that at all.
Try something more like this instead:
var
w, h: Integer;
pScanline: PByte;
Red, Green, Blue: Byte;
PalEntries: array[0..255] of TRGBQuad;
begin
Result := False;
if aBitmap.Palette <> 0 then
GetPaletteEntries(aBitmap.Palette, 0, 255, PalEntries);
for h := 0 to aBitmap.Height - 1 do
begin
pScanline := PByte(aBitmap.Scanline[h]);
for w := 0 to aBitmap.Width - 1 do
begin
case aBitmap.PixelFormat of
pf8Bit: begin
Blue := PalEntries[pScanline^].rgbBlue;
Green := PalEntries[pScanline^].rgbGreen;
Red := PalEntries[pScanline^].rgbRed;
Inc(pScanline);
end;
pf24Bit: begin
Blue := PRGBTriple(pScanline).rgbtBlue;
Green := PRGBTriple(pScanline).rgbtGreen;
Red := PRGBTriple(pScanline).rgbtRed;
Inc(pScanline, SizeOf(TRGBTriple));
end;
// etc for other color depths...
end;
end;
end;
end;
You need to do some research on how bitmaps actually work, particularly in relation to the format of the scanlines at the various color depths.
Bitmaps overview
I am using TPNGList from Gustavo Daud version 1.4 in Delphi XE2
It is holding some PNG 256x256 images that I use as button images.
However there is a need to change the background color and the contrast of the image is not good.
So I have now White images for dark backgrounds.
I need to change them to Black for light backgrounds.
There is transparency and should be kept. There is only white pixels. But, a generic source to target function would be great either.
EDIT:
Following sugestion for "go for it" I have tried the following, but only get black or white boxes:
procedure PNGInvertWB(Image: TPngImage; AWhite: Boolean);
procedure WBInvertRGB(var R, G, B: Byte);
var
color: LongInt;
begin
if AWhite then
begin
if RGB(R, G, B) = clWhite then
begin
Color := ColorToRGB(clBlack);
R := GetRValue(Color);
G := GetGValue(Color);
B := GetBValue(Color);
end;
end
else
begin
if RGB(R, G, B) = clBlack then
begin
Color := ColorToRGB(clWhite);
R := GetRValue(Color);
G := GetGValue(Color);
B := GetBValue(Color);
end;
end;
end;
var
X, Y, PalCount: Integer;
Line: PRGBLine;
PaletteHandle: HPalette;
Palette: array[Byte] of TPaletteEntry;
begin
if not (Image.Header.ColorType in [COLOR_GRAYSCALE, COLOR_GRAYSCALEALPHA]) then begin
if Image.Header.ColorType = COLOR_PALETTE then begin
PaletteHandle := Image.Palette;
PalCount := GetPaletteEntries(PaletteHandle, 0, 256, Palette);
for X := 0 to PalCount - 1 do
WBInvertRGB(Palette[X].peRed, Palette[X].peGreen, Palette[X].peBlue);
SetPaletteEntries(PaletteHandle, 0, PalCount, Palette);
Image.Palette := PaletteHandle;
end
else begin
for Y := 0 to Image.Height - 1 do begin
Line := Image.Scanline[Y];
for X := 0 to Image.Width - 1 do
WBInvertRGB(Line[X].rgbtRed, Line[X].rgbtGreen, Line[X].rgbtBlue);
end;
end;
end;
end;
I am calling this using this code:
procedure TDBNavigator.UpdateColor;
var
PNGImage: TPngImage;
HCColor : TColor;
procedure Invert(AImage: TImage; AWhite: boolean);
begin
ConvertToPNG(AImage.Picture.Graphic, PNGImage);
PNGInvertWB(PNGImage, not AWhite);
AImage.Picture.Graphic := PNGImage;
end;
begin
Color := ThemeManager.CurrentPallete.Color[FThemeColor];
HCColor := ThemeManager.CurrentPallete.HighContrast(FThemeColor);
if HCColor <> FCurrentColor then
begin
Invert(uiPrevious, HCColor = clWhite);
Invert(uiNext, HCColor = clWhite);
Invert(uiInsert, HCColor = clWhite);
Invert(uiPost, HCColor = clWhite);
Invert(uiCancel, HCColor = clWhite);
Invert(uiDelete, HCColor = clWhite);
Invert(uiRefresh, HCColor = clWhite);
FCurrentColor := HCColor;
end;
end;
Not sure which part is wrong. This is part of one component and I am trying to change the image that was assigned on design time. It was a PNG image that I have loaded, 256x256 with transparency.
I need to use that TImage, I know it is not a button. And probably there is components that do that. I need to make by myself because a specific library that I am using.
I got the PNGInvertWB idea from one of the Gustavo functions on PNGFunctions:
procedure MakeImageGrayscale(Image: TPngImage; Amount: Byte = 255);
So, I have no experience with images at all, what is wrong with this code?
This is how it looks like on the component where I have the images:
Original:
After:
I have used the following function from the PNGFunctions to try this:
procedure MakeImageGrayscale(Image: TPngImage; Amount: Byte = 255);
procedure GrayscaleRGB(var R, G, B: Byte);
var
X: Byte;
begin
X := Round(R * 0.30 + G * 0.59 + B * 0.11);
R := Round(R / 256 * (256 - Amount - 1)) + Round(X / 256 * (Amount + 1));
G := Round(G / 256 * (256 - Amount - 1)) + Round(X / 256 * (Amount + 1));
B := Round(B / 256 * (256 - Amount - 1)) + Round(X / 256 * (Amount + 1));
end;
var
X, Y, PalCount: Integer;
Line: PRGBLine;
PaletteHandle: HPalette;
Palette: array[Byte] of TPaletteEntry;
begin
//Don't do anything if the image is already a grayscaled one
if not (Image.Header.ColorType in [COLOR_GRAYSCALE, COLOR_GRAYSCALEALPHA]) then begin
if Image.Header.ColorType = COLOR_PALETTE then begin
//Grayscale every palette entry
PaletteHandle := Image.Palette;
PalCount := GetPaletteEntries(PaletteHandle, 0, 256, Palette);
for X := 0 to PalCount - 1 do
GrayscaleRGB(Palette[X].peRed, Palette[X].peGreen, Palette[X].peBlue);
SetPaletteEntries(PaletteHandle, 0, PalCount, Palette);
Image.Palette := PaletteHandle;
end
else begin
//Grayscale every pixel
for Y := 0 to Image.Height - 1 do begin
Line := Image.Scanline[Y];
for X := 0 to Image.Width - 1 do
GrayscaleRGB(Line[X].rgbtRed, Line[X].rgbtGreen, Line[X].rgbtBlue);
end;
end;
end;
end;
Where I have changed the GrayscaleRGB, since it was getting each pixel and changing it to gray scale, so I believed I could change to black or white accordingly.
will be good to see what you have original a what you get after this code execute
and the question is:
Do you really be sure that color of whole background is equ to black or white or it is near black or near white?
Becouse you compare exact color RGB(R, G, B) = clWhite
try this
procedure WBInvertRGB(var R, G, B: Byte);
var
color: LongInt;
begin
if AWhite then
begin
if ((R>240) and (G>240) and (B>240)) then
begin
Color := ColorToRGB(clBlack);
R := GetRValue(Color);
G := GetGValue(Color);
B := GetBValue(Color);
end;
end
else
begin
if ((R<15) and (G<15) and (B<15)) then
begin
Color := ColorToRGB(clWhite);
R := GetRValue(Color);
G := GetGValue(Color);
B := GetBValue(Color);
end;
end;
end;
what you get after?
Then i suppose that this is becouse of transparency (change bottom left corner to some different color)
i do not know details about this png component and do not know if it have pixels property
but put this at the end of procedure
Line := Image.Scanline[Image.Height - 1];
Line[0].rgbtRed:= 5;
Line[0].rgbtGreen:= 5;
Line[0].rgbtBlue:= 5;
any change in result?
or you go inside palete code than full colors
put breakpoints and you will see where your code break
and add comment here if this is color palete -this make difference becouse you can not change color to white and another to black. You need three steeps one change white to e.g. red black to white and red to black
Gretings to all!
How to print pictures in Delphi on TPrinter, in real sizes of pictures?
From canvas of TImage I have good results, but if I paints on TPrinter canvas, I have BAD result, puctures is too small than real size of bitmap.
Why that happens What I'm need to do for fix bug?
UPDATE
Yes, I seen question from the hint in the 1st post.
I can't use JCL/JVCL code in my project, but I got idea from it.
I create temporary TImage, and calculate dimensions of it in accordance with the factor of printer's DPI:
var
i, iRow, iCol, // Counter
iBorderSize, // Ident from left/top borders
iImgDistance, // Ident between images in grid
iRows, // Rows Count
iColumns, // Colun count
iLeft, iTop: Integer; // For calc
bmp: TBitmap;
bStop, bRowDone, bColDone: Boolean;
Img1: TImage;
scale: Double;
function CalcY: Integer;
begin
if (iRow = 1) then
Result := iBorderSize
else
Result := iBorderSize + (iImgDistance * (iRow - 1)) +
(bmp.Height * (iRow - 1));
end;
function CalcX: Integer;
begin
if (iCol = 1) then
Result := iBorderSize
else
Result := iBorderSize + (iImgDistance * (iCol - 1)) +
(bmp.Width * (iCol - 1));
end;
begin
iBorderSize := StrToInt(BorderSizeEdit.Text);
iImgDistance := StrToInt(ImgsDistanceEdit.Text);
iRows := StrToInt(RowsCountEdit.Text);
iColumns := StrToInt(ColCountEdit.Text);
iRow := 1;
iCol := 1;
iLeft := iBorderSize;
iTop := iBorderSize;
if Printer.Orientation = poPortrait then
scale := GetDeviceCaps(Printer.Handle, LOGPIXELSX) /
Screen.PixelsPerInch
else
scale := GetDeviceCaps(Printer.Handle, LOGPIXELSY) /
Screen.PixelsPerInch;
bmp := TBitmap.Create;
Img1 := TImage.Create(nil);
Img1.Height := Trunc(Printer.PageHeight / scale); //Calc canvas size
Img1.Width := Trunc(Printer.PageWidth / scale); //Calc canvas size
Img1.Canvas.Brush.Color := clWhite;
Img1.Canvas.FillRect(Rect(0, 0, Img1.Width, Img1.Height));
try
bmp.LoadFromFile(ExtractFilePath(ParamStr(0)) + 'Source.bmp');
for i := 1 to 18 do
begin
if (iRow <= iRows) then
begin
iTop := CalcY;
iLeft := CalcX;
Img1.Canvas.Draw(iLeft, iTop, bmp);
if not((iRow = iRows) and (iCol = iColumns)) then
begin
if (iCol = iColumns) then
begin
Inc(iRow);
iCol := 1;
end
else
Inc(iCol);
end
else
begin
PrintImage(Img1, 100);
iRow := 1;
iCol := 1;
Img1.Canvas.Brush.Color := clWhite;
Img1.Canvas.FillRect(Rect(0, 0, Img1.Width, Img1.Height));
end;
end;
end;
finally
FreeAndNil(bmp);
FreeAndNil(Img1);
end;
end;
And draw it on TPrinter.Canvas.
You can see results below:
Results is good, but not perfect.
As you can see, in the last column, all images are drawn not to the end, some part misses off the paper and not drawn.
I think it's happens because I use the Trunc to get integer part of double when I'm calculate dimensions of TImage.Canvas in accordance with the factor of printer's DPI.
By experiments I know value 0.20. 0.20 is a part of last column images, in pixels, that not drawn. If I change code, that gets scale factor by this:
if Printer.Orientation = poPortrait then
scale := GetDeviceCaps(Printer.Handle, LOGPIXELSX) /
Screen.PixelsPerInch - 0.20
else
scale := GetDeviceCaps(Printer.Handle, LOGPIXELSY) /
Screen.PixelsPerInch - 0.20;
I have that, what I need:
I think the value 0.20 isn't a constant and it will change on every PC.
How to calculate this value? What need to solve this problem?
The basic problem here is one of scaling. More or less, figure out how much to expand the resolution of the image and then stretchdraw it to the printer canvas. Something like this gets the image stretched out to the dimension of the printer canvas.
procedure TForm1.Button2Click(Sender: TObject);
var
MyRect: TRect;
scale: Double;
begin
if PrintDialog1.Execute then
begin
Printer.BeginDoc;
scale := Printer.PageWidth / Bitmap1.Width;
ShowMessage(FloatToStr(scale));
{ horizontal pixels, vertical pixels, bit depth 600 x 600 x 24}
MyRect.Left := 0;
MyRect.Top := 0;
MyRect.Right := trunc(Bitmap1.Width * scale);
MyRect.Bottom := trunc(Bitmap1.Height * scale);
Printer.Canvas.StretchDraw(MyRect, Bitmap1);
Printer.EndDoc;
end;
Of course, you have to check "Right" and "Bottom" to make sure they don't exceed your PageWidth and PageHeight depending on the type of scaling you use (6.25 or 600/96 seems fine for simply making an image the same relative size as the screen, assuming those numbers match your printer and screen), assuming you want to keep the image to one page and not mosaic pieces of it onto multiple pages.
I don't know if this works entirely since I don't have a varied number of devices (i.e. different DPIs) to test both orientations on, but this seems to be what you want to get both DPI numbers dynamically.
if Printer.Orientation = poPortrait then
scale := GetDeviceCaps(Printer.Handle, LOGPIXELSX) / PixelsPerInch
else
scale := GetDeviceCaps(Printer.Handle, LOGPIXELSY) / pixelsperinch;
Then of course, you multiply like above.
The issue you're running into is that there really isn't a "real size" of an image, it's all relative. The printer often has a lot higher resolution then your monitor and that's why pictures look small.
Your monitor has often a resolution of 96 dpi and normal printer has a resolution of 600 dpi which means your image prints in its real size it just looks small because a printer can put a lot more dots in the same space then a monitor can.
Delphi Basics link was also helpful : http://www.delphibasics.co.uk/RTL.asp?Name=printer&ExpandCode1=Yes
on form : drag n drop TPrintDialog from your Tool Palette
and manually add this to the uses clause under [Implementation]
uses printers; // Unit containing the printer command
With that and this post I was able to print directly to any printer at the size I wanted for images or text. There is no need to call the bitmap or assign the TPrinter once you have added the unit above. Just draw directly to the canvas in your PC printer queue.
procedure TForm1.cmdPrintCircleClick(Sender: TObject);
var
xx, yy, mySize : integer;
//printer1 : TPrinter;
begin
// create image directly on Printer Canvas and print it
//Ellipse( X-(Width div 2), Y-(Height div 2), X+(Width div 2), Y+(Height div 2));
if PrintDialog1.Execute then
try
with Printer do
begin
if Printer.Orientation = poPortrait then
begin
// represents 1/2 US-inch relative to Portrait page size 8.5 x 11
mySize := Trunc(PageWidth / 8.5 / 2);
end
else
begin
// represents 1/2 US-inch relative to Landscape page size 11 x 8.5
mySize := Trunc(PageHeight / 8.5 / 2);
end;
xx := Trunc(PageWidth / 2);
yy := Trunc(PageHeight / 2);
// Start printing
BeginDoc;
// Write out the ellipse // create one-inch black circle
Canvas.Brush.Color := clBlack;
Canvas.Ellipse(xx - mySize, yy - mySize, xx + mySize, yy + mySize);
// Finish printing
EndDoc;
end;
finally
end;
end;
I having one Delphi XE2 Project to change Label01 Font Color using Timer04. So I have written the following codes:
procedure TMainForm.FormCreate(Sender: TObject);
begin
Timer04.Enabled := true;
end;
..
..
..
..
..
procedure TMainForm.Timer04Timer(Sender: TObject);
var
StartColor, RedColor, GreenColor, BlueColor: integer;
begin
StartColor := ColorToRGB(Label01.Font.Color);
RedColor := GetRValue(StartColor);
GreenColor := GetGValue(StartColor);
BlueColor := GetBValue(StartColor);
if RedColor <= 251 then Inc(RedColor, 1) else RedColor := 1;
if GreenColor <= 252 then Inc(GreenColor, 2) else GreenColor := 2;
if BlueColor <= 253 then Inc(BlueColor, 3) else BlueColor := 3;
Label01.Font.Color := RGB(RedColor, GreenColor, BlueColor);
end;
This codes work perfectly. Label01 Font Color changes between different colors.
Now I am trying to implement that Label02 Color will be fixed (say Green) and the value of brightnees will be increase from 0 to 100. If the value reaches to 100 it will be decreased to 0 and it will be continuous to a loop.
For my case I have chosen HUE=135, SATURATION=85 and BRIGHTNESS=50. The value of BRIGHTNESS will be increased from 50 to 100 and then will be decreased from 100 to 0 and it will be continued.
But the problem is that there is no such Function available to convert HSB to RGB and vice versa in Delphi XE2. I have Gooled it. But I have found any Function as HSBToRGB. Only some Delphi Unit is availabe. I have read their revoews and found that every one is having some bugs.
Here is a Delphi a translation of C code found here: http://www.cs.rit.edu/~ncs/color/t_convert.html
function RGBFP(R, G, B: Double): TColor;
const
RGBmax = 255;
begin
Result := RGB(Round(RGBmax * R), Round(RGBmax * G), Round(RGBmax * B));
end;
function HSVtoRGB(H, S, V: Double): TColor;
var
i: Integer;
f, p, q, t: Double;
begin
Assert(InRange(H, 0.0, 1.0));
Assert(InRange(S, 0.0, 1.0));
Assert(InRange(V, 0.0, 1.0));
if S = 0.0 then
begin
// achromatic (grey)
Result := RGBFP(V, V, V);
exit;
end;
H := H * 6.0; // sector 0 to 5
i := floor(H);
f := H - i; // fractional part of H
p := V * (1.0 - S);
q := V * (1.0 - S * f);
t := V * (1.0 - S * (1.0 - f));
case i of
0:
Result := RGBFP(V, t, p);
1:
Result := RGBFP(q, V, p);
2:
Result := RGBFP(p, V, t);
3:
Result := RGBFP(p, q, V);
4:
Result := RGBFP(t, p, V);
else
Result := RGBFP(V, p, q);
end;
end;
I've given this minimal testing. Please do feel free to double check it.
For Firemonkey it's HSLtoRGB from System.UIConsts.pas
It's similar to HSB (=HSV). All you can do with HSB you can do with HSL
function HSLtoRGB(H, S, L: Single): TAlphaColor;
It returns FMX TAlphaColor - that is RGB.
For VCL you need TColor, that is BGR.
So use RGBtoBGR in the same unit.
function RGBtoBGR(const C: TAlphaColor): TAlphaColor;
Then just do
Color := TColor(MyAlphaColorVariable);
AFAIK there is no HSB function in standard units.
Can anyone help me in finding a component or SDK that rotates PNG images quickly whilst retaining transparency?
The original author of the PNGImage component (the basis of the Delphi native component) had a forum where he, and others, posted code snippets on how to do things using the PNGImage component.
Before the forum was taken down I grabbed a copy of all of the code snippets and placed them on the CodeGear Code Central website.
Most if not all of these work with native PNG images and do maintain the Alpha channel.
Here is the complete list of examples included in the package:
Smooth rotates a PNG object
Resizes a TPNGObject using a smooth algorithm
Slice one PNG into several smaller ones
Saves an image as either a bitmap or a png.
Sample chunk descendant
Read all tEXt-Chunks and write values into a TStrings object
Display a message box with information extracted from the PNG File
Finds and cuts a block from a PNG image
This method converts the png into a jpeg object
This method converts the png into a bmp object
Overlay one PNG over another
This makes the image half transparent
Flips a png image vertically and saves back
Draws a png image over the desktop
Here is the link: CodeCentral PNG Methods
EDIT
Here is a highly optimized version of a RotatePNG function.
It still, technically, goes pixel by pixel but it uses a number of techniques, such as scanlines, pointer math and storage of image dimensions to achieve a decent speed increase.
I used a 2550x3300 pixel image (~5MB) for testing and by using semi-rough, but totally (non)scientific calculations (counting in my head) I came up with the following metrics:
the old routine (mentioned above):~7 seconds
the new routine (code below):~1.5 seconds
I can't strictly take credit for this. The code originally came from from EFG's website and I decided to try my hand at converting one of those routines to rotate PNG images instead of bitmaps.
I'm sure anyone who knows more about this type of thing will take a look at the code and offer up some suggestions to tweak more speed out of it.
procedure RotatePNG(
const PNGOriginal:TPNGImage;//input PNG
out PNGRotated:TPNGImage; //output PNG
Const Angle : double);
{
(c) har*GIS L.L.C., 1999
You are free to use this in any way, but please retain this comment block.
Please email questions to jim#har-gis.com .
Doc & Updates: http://www.efg2.com/Lab/ImageProcessing/RotateScanline.htm
and http://www.efg2.com/Lab/Library/Delphi/Graphics/JimHargis_RotateBitMap.zip
}
{
Modified by R.J.Mills, 2012 -
- Use pointer arithmetic instead of type sub-scripting for faster pixels.
- Converted to rotate PNG images June 2012.
}
Type
TRGBTripleArray = array [0..32767] of TRGBTriple; //allow integer subscript
pRGBTripleArray = ^TRGBTripleArray;
VAR
wPng : TPngImage;
theta:Double; // rotn angle in radians counterclockwise in windows
cosTheta : Single; {in windows}
sinTheta : Single;
i : INTEGER;
iOriginal : INTEGER;
iPrime : INTEGER;
j : INTEGER;
jOriginal : INTEGER;
jPrime : INTEGER;
NewWidth,NewHeight:INTEGER;
nBytes: Integer;//no. bytes per pixelformat
Oht,Owi,Rht,Rwi: Integer;//Original and Rotated subscripts to bottom/right
RowSizeRot : integer;
RowSizeOrg : integer;
AlphaSizeRot : integer;
AlphaSizeOrg : integer;
RowStartPtr : Pointer;
AlphaStartPtr : Pointer;
RowRotatedT: pRGBtripleArray; //3 bytes
AlphaRowT: pByteArray; //1 byte
AlphaRotatedT : pByteArray; //1 byte
TransparentT: TRGBTriple;
{=======================================}
function Mod360( const angle:Double ):Double;
begin
result := frac( angle/360 )*360;
if result < 0 then
result := result+360;
end;
{=======================================}
begin
Theta := -(2*pi* Mod360(angle))/360;
sinTheta := SIN( theta );
cosTheta := COS( theta );
NewWidth := ABS( ROUND( PNGOriginal.Height*sinTheta) ) + ABS( ROUND( PNGOriginal.Width*cosTheta ) );
NewHeight := ABS( ROUND( PNGOriginal.Width*sinTheta ) ) + ABS( ROUND( PNGOriginal.Height*cosTheta) );
if ( ABS(theta)*MAX( PNGOriginal.width,PNGOriginal.height ) ) > 1 then
begin//non-zero rotation
wPng := TPngImage.createblank(PNGOriginal.Header.ColorType, 8, NewWidth, NewHeight);
try
//local constants for loop, each was hit at least width*height times 1/8/00
Rwi := NewWidth - 1; //right column index
Rht := NewHeight - 1;//bottom row index
Owi := PNGOriginal.Width - 1; //transp color column index
Oht := PNGOriginal.Height - 1; //transp color row index
RowRotatedT := wPng.Scanline[ Rht ] ;
RowStartPtr := PNGOriginal.Scanline[ 0 ];
RowSizeRot := Integer(wPng.ScanLine[1])-Integer(wPng.ScanLine[0]);
RowSizeOrg := Integer(PNGOriginal.ScanLine[1])-Integer(PNGOriginal.ScanLine[0]);
TransparentT := pRGBtripleArray( PNGOriginal.Scanline[ Oht ] )[0];
if PNGOriginal.Header.ColorType in [COLOR_RGBALPHA] then
begin
AlphaRowT := PNGOriginal.AlphaScanline[ Oht ];
AlphaStartPtr := PNGOriginal.AlphaScanline[ 0 ];
AlphaRotatedT := wPng.AlphaScanline[ Rht ];
AlphaSizeRot := Integer(wPng.AlphaScanline[1])-Integer(wPng.AlphaScanline[0]);
AlphaSizeOrg := Integer(PNGOriginal.AlphaScanline[1])-Integer(PNGOriginal.AlphaScanline[0]);
end
else
begin
AlphaRowT := nil;
AlphaStartPtr := nil;
AlphaRotatedT := nil;
AlphaSizeRot := 0;
AlphaSizeOrg := 0;
end;
for j := Rht downto 0 DO //1/8/00
begin //for j
jPrime := 2*j - NewHeight + 1 ;
for i := Rwi downto 0 DO //1/8/00
begin //for i
iPrime := 2*i - NewWidth + 1;
iOriginal := ( ROUND( iPrime*CosTheta - jPrime*sinTheta ) + Owi ) div 2;
if ( iOriginal >= 0 ) and ( iOriginal <= Owi ) then
begin //inside
jOriginal := ( ROUND( iPrime*sinTheta + jPrime*cosTheta ) + Oht ) div 2 ;
if ( jOriginal >= 0 ) and ( jOriginal <= Oht ) then
begin //1/8/00
RowRotatedT[i] := pRGBTripleArray(Integer(RowStartPtr) + (jOriginal * RowSizeOrg))[iOriginal];
if assigned(AlphaRotatedT) then
AlphaRotatedT[i] := pByteArray(Integer(AlphaStartPtr) + (jOriginal * AlphaSizeOrg))[iOriginal];
end
else
begin //set Transparent
if Assigned(AlphaRotatedT) then
AlphaRotatedT[i] := 0;
RowRotatedT[i] := TransparentT;
end;
end //inside
else
begin //Set Transpaarent;
if Assigned(AlphaRotatedT) then
AlphaRotatedT[i] := 0;
RowRotatedT[i] := TransparentT;
end;
end; //for i
Dec(Integer(RowRotatedT), RowSizeRot) ;
if assigned(AlphaRotatedT) then
Dec(Integer(AlphaRotatedT), AlphaSizeRot) ;
end;//for j
PNGRotated.Assign(wPng);
finally
wPng.Free;
end;
end //non-zero rotation
else
begin //Zero rotation
if PNGRotated <> PNGOriginal then
PNGRotated.Assign(PNGOriginal);
end;
end; {RotatePNG}