converting a PNGImage to grayscale using delphi

2019-03-21 11:22发布

问题:

hi there here it is my code:

procedure TForm4.Button1Click(Sender: TObject);
var
  png: TPNGImage;
  data: PRGBQarray;
  p: ^tagRGBQuad;
  i, o: integer;
begin
  png := TPNGImage.Create;
  try
    png.LoadFromFile('C:\Untitled.png');
    for o := 1 to 100 do
    begin
      data:=png.Scanline[o];
      for I := 1 to 400 do
      begin
        p := @data^[i];
        p.rgbGreen := p.rgbBlue;
        p.rgbRed := p.rgbGreen;
      end;
    end;
    img.picture.Assign(png);
  finally
    png.Free;
  end;
end;

it doesn't work and it makes the pic messy, I'm sure it's because of the rgbReserved. what should i do?

回答1:

This is how to greyify a bitmap. (And, yes, if you want to greyify a PNG, you first need to get the bitmap data out of it. I think the VCL will do this for you.)

type
  PRGB32Array = ^TRGB32Array;
  TRGB32Array = packed array[0..MaxInt div SizeOf(TRGBQuad)-1] of TRGBQuad;

procedure MakeGrey(Bitmap: TBitmap);
var
  w, h: integer;
  y: Integer;
  sl: PRGB32Array;
  x: Integer;
  grey: byte;
begin
  Bitmap.PixelFormat := pf32bit;
  w := Bitmap.Width;
  h := Bitmap.Height;
  for y := 0 to h - 1 do
  begin
    sl := Bitmap.ScanLine[y];
    for x := 0 to w - 1 do
      with sl[x] do
      begin
        grey := (rgbBlue + rgbGreen + rgbRed) div 3;
        rgbBlue := grey;
        rgbGreen := grey;
        rgbRed := grey;
      end;
  end;
end;

Sample usage:

procedure TForm4.Button1Click(Sender: TObject);
var
  bm: TBitmap;
begin
  bm := TBitmap.Create;
  try
    bm.LoadFromFile('C:\Users\Andreas Rejbrand\Pictures\Porträtt, litet, kvadratiskt.bmp');
    MakeGrey(bm);
    Canvas.Draw(0, 0, bm);
  finally
    bm.Free;
  end;
end;


回答2:

Andreas's answer will give you a good, fast approximation, but you'll lose some quality, because red, green and blue don't mix with equal intensities in the human eye. If you want to "get it right", instead of

grey := (rgbBlue + rgbGreen + rgbRed) div 3;

try this:

grey := round(rgbRed * .3) + round(rgbGreen * .59) + round(rgbBlue * .11);

You'll get a bit of a performance hit over the simple average, though it probably won't be noticeable unless you're on a very large image.



回答3:

I know the question has already been answered but here is my 2c worth...

The following code comes from the PNGComponents package (PngFunctions.pas) produced by Thany.

//
//The Following code comes from the PNGComponents package from Thany...
//
procedure MakeImageGrayscale(Image: TPNGObject; 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: Pointer;
   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(PRGBLine(Line)^[X].rgbtRed, PRGBLine(Line)^[X].rgbtGreen, PRGBLine(Line)^[X].rgbtBlue);
             end;
          end;
     end;
end;

There is a set of routines, that was originally published by the author of the PNGImage components, that can be found on Code Central that shows how to do other things like Alpha blending two images, rotation, overlay, etc. CodeCentral Link



回答4:

This really should have been a comment to @Mason's routine to turn RGB into GreyScale, but since I don't know how to make a comment show code, I'm making it an answer instead.

This is how I do the conversion:

FUNCTION RGB2GRAY(R,G,B : BYTE) : BYTE; Register; ASSEMBLER;
  ASM
                IMUL    EAX,19595
                IMUL    EDX,38470
                IMUL    ECX,7471
                ADD     EAX,EDX
                ADD     EAX,ECX
                SHR     EAX,16
  END;

FUNCTION GreyScale(C : TColor) : TColor; Register; ASSEMBLER;
  ASM
                MOVZX   EDX,AH
                MOV     ECX,EAX
                SHR     ECX,16
                MOVZX   EAX,AL
                CALL    RGB2GRAY
                MOVZX   EAX,AL
                MOV     AH,AL
                SHL     EAX,8
                MOV     AL,AH
  END;

I don't know if it is NTSC formula or whatever, but they seem to work in my programs :-).



回答5:

Why don't you just assign it to a TJPEGImage, set the GrayScale property of the JPEG to true and then assign back to TPNGImage?!