Recent

Author Topic: Working (resizing, padding, splitting) with 16 bit png and 8bit png images?  (Read 843 times)

FiftyTifty

  • Jr. Member
  • **
  • Posts: 56
I've been pulling my hair out with trying to get a tool made to work with 16 bit greyscale png, as well as 24 bit colour png. I've given up on doing it with Python, as the libraries that work with 16bit png files are all sorts of wonky and have incorrect documentation.

After doing some research, it seems that only Vampyre Imaging Library works with 16 bit greyscale png files, so I'm using that for now. If there's a better one, let me know.

What I have to do, is take a 4096x4096 image (image divisible by 256), and pad out the bottom & right sides of the image with black until it is 4112x4112 (divisible by 257). Then, split the image into 257x257 images by x&y coordinates.

How do I pad the image from being 4096x4096, into 4112x4112? My main idea was to create two images, fill them with black, and append them to the original.

Don't see a way to do that though.

Here is my current code:

Code: Pascal  [Select][+][-]
  1. procedure TformWindow.buttonHeightmapClick(Sender: TObject);
  2. var
  3.    imageCurrent, imageResized, imageFillX, imageFillY: TSingleImage;
  4.    iSizeX, iSizeY, iAddX, iAddY: Integer;
  5.    strImageToSave: string;
  6.    infoCurrent: TImageFormatInfo;
  7. begin
  8.  
  9.   if dialogOpen.Execute then begin
  10.  
  11.     imageCurrent := TSingleImage.CreateFromFile(dialogOpen.FileName);
  12.     strImageToSave := ExtractFileNameWithoutExt(dialogOpen.FileName);
  13.  
  14.     imageResized := TSingleImage.CreateFromImage(imageCurrent);
  15.     iSizeX := imageCurrent.Height;
  16.     iSizeY := imageCurrent.Width;
  17.  
  18.     GetImageFormatInfo(imageCurrent.Format, infoCurrent);
  19.  
  20.     //We want: 'png' = DetermineFileFormat(dialogOpen.FileName)
  21.     //16 bit png: infoCurrent.BytesPerPixel = 2;
  22.     //Greyscale 1 ChannelAlpha: infoCurrent.ChannelCount = 1;
  23.  
  24.     ShowMessage(DetermineFileFormat(dialogOpen.FileName) + ' '
  25.       + inttostr(infoCurrent.BytesPerPixel * 8) + 'bit '
  26.       + inttostr(infoCurrent.ChannelCount) + ' channel'
  27.     );
  28.  
  29.     iAddX := 257 - (iSizeX mod 257);
  30.     iAddY := 257 - (iSizeY mod 257);
  31.  
  32.     imageFillX := TSingleImage.CreateFromParams(257, iAddY, imageCurrent.Format);
  33.     imageFillY := TSingleImage.CreateFromParams(iAddX, 1, imageCurrent.Format);
  34.  
  35.     //Somehow pad out imageCurrent until it is 4112x4112
  36.  
  37.   end;
  38.  
  39.  
  40. end;  

circular

  • Hero Member
  • *****
  • Posts: 4220
    • Personal webpage
Hello FiftyTifty,

You could do that thing you want to do by creating an image for each target square of the grid and put the content of the source image in it with the appropriate offset.

I don't know how to do it with Vampyre Imaging Library, though I can tell you how to do it with BGRABitmap. It provides a TExpandedBitmap class that has 16bit per channel. It is not grayscale but RGBA, so it will used 4x more memory. But it can read and write 16-bit ("word-sized") grayscale PNG images.

Code: Pascal  [Select][+][-]
  1. uses ExpandedBitmap, BGRABitmapTypes, BGRAWritePNG;
  2.  
  3. procedure GetSquares(ASourceFile: string; ADestFilePrefix: string; ATileSize: integer = 257);
  4. var
  5.   source, dest: TExpandedBitmap;
  6.   ofsX, ofsY, nbX, nbY, ix, iy: integer;
  7.   writer: TBGRAWriterPNG;
  8. begin
  9.   source := TExpandedBitmap.Create;
  10.   source.LoadFromFile(ASourceFile);
  11.   nbX := (source.Width+ATileSize-1) div ATileSize;
  12.   nbY := (source.Width+ATileSize-1) div ATileSize;
  13.   ofsX := (nbX*ATileSize - source.Width) div 2;
  14.   ofsY := (nbY*ATileSize - source.Height) div 2;
  15.   writer := TBGRAWriterPNG.Create;
  16.   writer.GrayScale := true;
  17.   writer.WordSized := true;
  18.   writer.UseAlpha := false;
  19.   for iy := 0 to nbY-1 do
  20.   for ix := 0 to nbX-1 do
  21.   begin
  22.     dest := TExpandedBitmap.Create(ATileSize, ATileSize, BGRABlack);
  23.     dest.PutImage(ofsX - ix*ATileSize, ofsY - iy*ATileSize, source, dmSet);
  24.     dest.SaveToFile(ChangeFileExt(ADestFilePrefix, '') +
  25.       IntToStr(ix) + '-' + IntToStr(iy) + '.png', writer);
  26.     dest.Free;
  27.   end;
  28.   writer.Free;
  29.   source.Free;
  30. end;

Regards
Conscience is the debugger of the mind

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11453
  • FPC developer.
Afaik fcl-image that comes with FPC can do 16-bit grayscale ?

If you get stuck somewhere with fcl-image, please talk about it here, or file a bug.

FiftyTifty

  • Jr. Member
  • **
  • Posts: 56
Afaik fcl-image that comes with FPC can do 16-bit grayscale ?

If you get stuck somewhere with fcl-image, please talk about it here, or file a bug.

Thanks for the info you two. I'll try with FCLImage first, as it appears to have the widest file format support. And actual examples on using it.

The first issue I've found is pen.FPColor for setting the colour of the black rectangle. There is no example in the wiki of setting a colour. How do we do that? In this case, I want it to be pure black (0).

Here's the code so far:

Code: Pascal  [Select][+][-]
  1.   if dialogOpen.Execute then begin
  2.  
  3.     imageCurrent := TFPMemoryImage.LoadFromFile(dialogOpen.FileName);
  4.     canvasCurrent := TFPImageCanvas.create(imageCurrent);
  5.     writerCurrent := TFPWriterPNG.Create;
  6.  
  7.     //Get size of the heightmap
  8.     iSizeX := imageCurrent.Height;
  9.     iSizeY := imageCurrent.Width;
  10.  
  11.     //Get number of pixels needed to make image divisible by 257
  12.     iAddX := 257 - (iSizeX mod 257);
  13.     iAddY := 257 - (iSizeY mod 257);
  14.  
  15.     //Set canvas to the size we want
  16.     canvasCurrent.Width := iSizeX + iAddX;
  17.     canvasCurrent.Height := iSizeY + iAddY;
  18.  
  19.     //Fill added rows with black
  20.     with canvas do begin
  21.  
  22.       pen.mode := pmBlack;
  23.       pen.style := psSolid;
  24.       pen.width := 1;
  25.       pen.FPColor := ;
  26.  
  27.     end;
  28.  
  29.     canvasCurrent.FillRect(iSizeX + 1, iSizeY + 1,
  30.                            canvasCurrent.Width, canvasCurrent.Height
  31.                            );
  32.  
  33.   end;

Right, did some more work. Now I got it to output an image, but it's a total mess. Mostly black with a couple strips of the source image coming through. I'll attach the project and the heightmap I'm using.

Here's the resulting heightmap: (https://i.imgur.com/ydZO386.png)

Project: https://mega.nz/file/XwNCmayZ#0MuwF4X-t75jXlccRxVL5N3ZCqKn984R3yr0HON1uf0

Code:

Code: [Select]
procedure TformWindow.buttonHeightmapClick(Sender: TObject);
var
   imageCurrent: TFPCustomImage;
   canvasCurrent: TFPCustomCanvas;
   readerCurrent: TFPCustomImageReader;
   writerCurrent: TFPCustomImageWriter;
   iSizeX, iSizeY, iAddX, iAddY: Integer;
   strImageCurrent, strImageToSave: string;
begin

  if dialogOpen.Execute then begin

    strImageCurrent := dialogOpen.FileName;
    imageCurrent := TFPMemoryImage.Create(0, 0);
    imageCurrent.LoadFromFile(strImageCurrent);
    canvasCurrent := TFPImageCanvas.create(imageCurrent);
    writerCurrent := TFPWriterPNG.Create;

    //Get size of the heightmap
    iSizeX := imageCurrent.Height;
    iSizeY := imageCurrent.Width;

    //Get number of pixels needed to make image divisible by 257
    iAddX := 257 - (iSizeX mod 257);
    iAddY := 257 - (iSizeY mod 257);

    //Set canvas to the size we want
    canvasCurrent.Width := iSizeX + iAddX;
    canvasCurrent.Height := iSizeY + iAddY;

    //Fill added rows with black
    with canvas do begin

      pen.mode := pmBlack;
      pen.style := psSolid;
      pen.width := 1;
      pen.FPColor := TColorToFPColor(RGBToColor(0,0,0));

    end;

    ShowMessage('Canvas set');

    canvasCurrent.FillRect(iSizeX + 1, iSizeY + 1,
                           canvasCurrent.Width, canvasCurrent.Height
                           );

    imageCurrent.SaveToFile('Test.png', writerCurrent);

  end;

  writerCurrent.Free;
  readerCurrent.Free;
  canvasCurrent.Free;
  imageCurrent.Free;

  ShowMessage('Free finished!');


end;   

Edit2: Right, it turns out that resizing the canvas via:

Code: Pascal  [Select][+][-]
  1.     canvasCurrent.Width := iSizeX + iAddX;
  2.     canvasCurrent.Height := iSizeY + iAddY;

Is what corrupts the image. Not the rectangle being drawn.

Edit3: Same happens with:

Code: Pascal  [Select][+][-]
  1.     imageCurrent.SetSize(iSizeX + iAddX, iSizeY + iAddY);
« Last Edit: January 09, 2023, 01:17:33 am by FiftyTifty »

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11453
  • FPC developer.
I don't use canvas for image operations. 

Whatever library I use, I get dimensions, bpp, rowpitch (*) and some format enumerator. Then I get line pointers, and simply use fillword/char etc.

(*) rowpitch is the jump between lines. which usually is (pixelsx*bpp) rounded up to 32-bit, but can be negative for bottom-up images.

wp

  • Hero Member
  • *****
  • Posts: 11916
Here's the resulting heightmap: (https://i.imgur.com/ydZO386.png)

Project: https://mega.nz/file/XwNCmayZ#0MuwF4X-t75jXlccRxVL5N3ZCqKn984R3yr0HON1uf0
Please don't store attachments on external servers where they will be gone sooner or later. Then this thread will become useless to anybody. And I don't want to register at the mega.nz site just to have a look at your project.

Pack the project files (*.pas, *.lfm, *.lpi, *.lpr plus required data files) into a single zip which you can upload under "Attachments and other options". There is a maximum upload limit which may become problematic with large projects, but you should be aware that the chance for somebody to look at your project decreases drastically with the size of your project.

The image may be an exception to this rule since it is very large and will probably exceed the upload limit.

I downloaded the image from the imgur server, but noticed that it is a 24bit image - I thought you are talking of 16-bit grayscale.

FiftyTifty

  • Jr. Member
  • **
  • Posts: 56
I don't use canvas for image operations. 

Whatever library I use, I get dimensions, bpp, rowpitch (*) and some format enumerator. Then I get line pointers, and simply use fillword/char etc.

(*) rowpitch is the jump between lines. which usually is (pixelsx*bpp) rounded up to 32-bit, but can be negative for bottom-up images.

That sounds pretty complex. Are there any tutorials on doing that with Lazarus?

-snip-

Yeah the image is converted to 8 bit RGB by imgur, and any image host for that matter. The source image is 16 bit greyscale, which you'd see if you downloaded the archive which contains the image. And it's too large to zip and upload, as the image is 11MB, and the project around 30MB.

 

TinyPortal © 2005-2018