Forum > Graphics

Working (resizing, padding, splitting) with 16 bit png and 8bit png images?

(1/2) > >>

FiftyTifty:
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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TformWindow.buttonHeightmapClick(Sender: TObject);var   imageCurrent, imageResized, imageFillX, imageFillY: TSingleImage;   iSizeX, iSizeY, iAddX, iAddY: Integer;   strImageToSave: string;   infoCurrent: TImageFormatInfo;begin   if dialogOpen.Execute then begin     imageCurrent := TSingleImage.CreateFromFile(dialogOpen.FileName);    strImageToSave := ExtractFileNameWithoutExt(dialogOpen.FileName);     imageResized := TSingleImage.CreateFromImage(imageCurrent);    iSizeX := imageCurrent.Height;    iSizeY := imageCurrent.Width;     GetImageFormatInfo(imageCurrent.Format, infoCurrent);     //We want: 'png' = DetermineFileFormat(dialogOpen.FileName)    //16 bit png: infoCurrent.BytesPerPixel = 2;    //Greyscale 1 ChannelAlpha: infoCurrent.ChannelCount = 1;     ShowMessage(DetermineFileFormat(dialogOpen.FileName) + ' '      + inttostr(infoCurrent.BytesPerPixel * 8) + 'bit '      + inttostr(infoCurrent.ChannelCount) + ' channel'    );     iAddX := 257 - (iSizeX mod 257);    iAddY := 257 - (iSizeY mod 257);     imageFillX := TSingleImage.CreateFromParams(257, iAddY, imageCurrent.Format);    imageFillY := TSingleImage.CreateFromParams(iAddX, 1, imageCurrent.Format);     //Somehow pad out imageCurrent until it is 4112x4112   end;  end;  

circular:
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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---uses ExpandedBitmap, BGRABitmapTypes, BGRAWritePNG; procedure GetSquares(ASourceFile: string; ADestFilePrefix: string; ATileSize: integer = 257);var  source, dest: TExpandedBitmap;  ofsX, ofsY, nbX, nbY, ix, iy: integer;  writer: TBGRAWriterPNG;begin  source := TExpandedBitmap.Create;  source.LoadFromFile(ASourceFile);  nbX := (source.Width+ATileSize-1) div ATileSize;  nbY := (source.Width+ATileSize-1) div ATileSize;  ofsX := (nbX*ATileSize - source.Width) div 2;  ofsY := (nbY*ATileSize - source.Height) div 2;  writer := TBGRAWriterPNG.Create;  writer.GrayScale := true;  writer.WordSized := true;  writer.UseAlpha := false;  for iy := 0 to nbY-1 do  for ix := 0 to nbX-1 do  begin    dest := TExpandedBitmap.Create(ATileSize, ATileSize, BGRABlack);    dest.PutImage(ofsX - ix*ATileSize, ofsY - iy*ATileSize, source, dmSet);    dest.SaveToFile(ChangeFileExt(ADestFilePrefix, '') +      IntToStr(ix) + '-' + IntToStr(iy) + '.png', writer);    dest.Free;  end;  writer.Free;  source.Free;end;
Regards

marcov:
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:

--- Quote from: marcov on January 08, 2023, 03:31:29 pm ---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.

--- End quote ---

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  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---  if dialogOpen.Execute then begin     imageCurrent := TFPMemoryImage.LoadFromFile(dialogOpen.FileName);    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 := ;     end;     canvasCurrent.FillRect(iSizeX + 1, iSizeY + 1,                           canvasCurrent.Width, canvasCurrent.Height                           );   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: ---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;   
--- End code ---

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


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---    canvasCurrent.Width := iSizeX + iAddX;    canvasCurrent.Height := iSizeY + iAddY;
Is what corrupts the image. Not the rectangle being drawn.

Edit3: Same happens with:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---    imageCurrent.SetSize(iSizeX + iAddX, iSizeY + iAddY);

marcov:
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.

Navigation

[0] Message Index

[#] Next page

Go to full version