PDA

View Full Version : Native PNG Loader with no 3rd party requirements



jdarling
15-07-2010, 12:58 PM
EDIT: Updated code and screen caps for reference

Ok, we have all seen the posts of want for a simple native PNG loader that supports FPC and doesn't require a ton o'crap along with it. So, I got off my duff and wrote one. It still has a few minor issues (see the screen shot below) regarding colors (green is off for some reason) but it works and should be a good starting point.

The code is based on previous works from zgl, BX, several C/C++ libraries, and hours spent reading the spec (talk about long winded ways to explain things like CRC32)...

This code ONLY LOADS PNG's from file or from a TStream, no saving (sorry WILL). It only requires classes (for stream support) and paszlib (for decompression).

Download at: http://www.eonclash.com/gl/uPNGSupport.pas

Here is how I'm testing it with OpenGL:

Loading a PNG file from disk:


png := TRawPNG.Create(loadFileName);
Writeln(loadFileName, ': ', png.LoadErrorMessage);
WriteLn('Chunk Info:');
for i := 0 to png.NumChunks-1 do
WriteLn(' ', png.ChunkName[i], ' of size ', png.ChunkAt[i]^.Length);
WriteLn('tEXt: ', png.tEXt);


Converting the PNG to an OpenGL texture:


glShadeModel(GL_SMOOTH);
glGenTextures(1, @FGLImage);
glBindTexture(GL_TEXTURE_2D, FGLImage);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, png.Width, png.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, png.Data );


Displaying the loaded PNG in OpenGL:


glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glLoadIdentity();
glTranslatef(1.5,0.0,-6.0);//1.5
if(png.BackgroundColor.C > 0)then
glColor4f(png.BackgroundColor.R/255, png.BackgroundColor.G/255, png.BackgroundColor.B/255, png.BackgroundColor.A/255)
else
glColor4f(1.0, 1.0, 1.0, 1.0);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, FGLImage);
glRotatef(-rotation, 0.0, 1.0, 0.0);
glBegin(GL_QUADS);
glTexCoord2f(0.0, 1.0);
glVertex3f(-1.0, 1.0, 0.0);
glTexCoord2f(1.0, 1.0);
glVertex3f( 1.0, 1.0, 0.0);
glTexCoord2f(1.0, 0.0);
glVertex3f( 1.0,-1.0, 0.0);
glTexCoord2f(0.0, 0.0);
glVertex3f(-1.0,-1.0, 0.0);
glEnd();
glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
glDisable(GL_TEXTURE_2D);


And here is a screenshot (with a triangle displayed using glColor3f). You will notice that the square's colors just don't quite add up, they should be white (top left), blue (top right), green (bottom left), red (bottom right) of the same intensity as the triangle :(... Any ideas I'd love to hear them. I still have to implement Gama Correction in the loading algo, and thats on the plans just isn't quite complete and need to implement the rest of the Chunk Types, but it loads any PNG I've thrown at it thus far. (NOW FIXED)

http://www.eonclash.com/gl/pngloadertest.jpg

- Jeremy

jdarling
15-07-2010, 02:12 PM
Ok, so, I feel sheepish... Bit of looking at my own GL code and realized I did the binding wrong ::)

Here is the proper GL init stuff:

glGenTextures(1, @FGLImage); // THIS WAS WRONG, it was pointed at png.data :(
glBindTexture(GL_TEXTURE_2D, FGLImage);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, png.Width, png.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, png.Data );


Here is another screenshot showing the fixed gamma and color space:
http://www.eonclash.com/gl/pngloadertest2.jpg

- Jeremy

User137
15-07-2010, 02:51 PM
I tried this unit with Next3D. Took some jpg, scaled it down to 512x1024 and saved as PNG using Gimp and max compression. Result was correct image when it comes to colors but it only loaded correctly 5% of the top part line by line and it drew it upside down. Rest 95% of the image was the same as last read line.

I tried with my original engine code (freepascal's unit) first which drew it without problems.

Edit: Now.. explained the format bit wrong. Later i added pixel wide corners and some text by hand using MsPaint and saved it in PNG again. That is different format than what gimp used and caused the above explained behavior.

But now i re-saved the same image with Gimp max compression and the loader give access violation. I commented out everything else so it's certainly in the png unit. It pops cpu window so i can't debug exactly where.

jdarling
15-07-2010, 03:44 PM
@User137: Can you post the image file that your using to break things? I'd like to have it to work with :)

@Anyone Interested: Updated one more time, just a few minor tweaks. Here is the current list of supported chunks:

IHDR - PNG Header
PLTE - Color Palette Chunk
IDAT - Image Data Chunk
gAMA - Gamma Correction Chunk
tEXt - Text Chunk
zTXt - Text Chunk
iTXt - Text Chunk
bKGD - Background Color Chunk
tRNS - Transparency Chunk, updated for proper support now

Download and links in first post have been updated.

- Jeremy

jdarling
15-07-2010, 05:12 PM
Well, found my issue with the coloring.... Guess it helps if you setup the proper color before you render your image. I had the color setup wrong, now using the background color provided or white (as a default) everything works great.

Code should be:


glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glLoadIdentity();
glTranslatef(0.1,0.0,-6.0);//1.5
if(png.BackgroundColor.C > 0)then
glColor4f(png.BackgroundColor.R/255, png.BackgroundColor.G/255, png.BackgroundColor.B/255, png.BackgroundColor.A/255)
else
glColor4f(1.0, 1.0, 1.0, 1.0);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, FGLImage);
glRotatef(-rotation, 0.0, 1.0, 0.0);
glBegin(GL_QUADS);
glTexCoord2f(0.0, 1.0);
glVertex3f(-1.0, 1.0, 0.0);
glTexCoord2f(1.0, 1.0);
glVertex3f( 1.0, 1.0, 0.0);
glTexCoord2f(1.0, 0.0);
glVertex3f( 1.0,-1.0, 0.0);
glTexCoord2f(0.0, 0.0);
glVertex3f(-1.0,-1.0, 0.0);
glEnd();
glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
glDisable(GL_TEXTURE_2D);


- Jeremy

noeska
15-07-2010, 05:18 PM
Nice one, i thought i was stuck on using an .dll or .so file with using png.
Can i use your code with code that is under the mozilla public license?

jdarling
15-07-2010, 05:24 PM
I'm not sticking no stinkin licenses on this, so as far as I care use it as you wish :). But, to quote, "Beware of bugs in my code; I have only proved it correct, not tried it." The only thing you can't do is take away my right to publish it anyone and everywhere LOL

I think this is something we as a community need, and its definitely been a stumbling block for me for long enough :).

What I would love to see, and what I plan on building myself as I get time, is a support unit like this for other formats as well. Would be nice to have ones for TGA, BMP, and SVG. Though I can't imagine that SVG could be put into a single stand alone simple to use unit.

- Jeremy

noeska
15-07-2010, 05:42 PM
on the svg matter take a look at my gl vector graphics project:
http://www.pascalgamedevelopment.com/forum/index.php?topic=5916.0
but that does not read physical svg files (yet)

User137
15-07-2010, 05:43 PM
Testing this kind now: http://next3d.webs.com/test1.png

It didn't crash but dimensions are 1024x512 so opposite than previous. Didn't try your new version yet...

Edit: Now run that with new version but it didn't make difference. This is how it looks in program:
http://i32.tinypic.com/ibe5bt.jpg

WILL
15-07-2010, 06:12 PM
This code ONLY LOADS PNG's from file or from a TStream, no saving (sorry WILL). It only requires classes (for stream support) and paszlib (for decompression).

:'( Well if you have the decoding working for the decompression to load it into memory, would it not be a simple task to encode it back for saving? Maybe when I get back from vacation, I could take a look at your code for decompression and play with it. That is if it's exposed and not in a separate API. ;)

jdarling
15-07-2010, 06:22 PM
Testing this kind now: http://next3d.webs.com/test1.png

It didn't crash but dimensions are 1024x512 so opposite than previous. Didn't try your new version yet...

Edit: Now run that with new version but it didn't make difference. This is how it looks in program:
http://i32.tinypic.com/ibe5bt.jpg


Ahh, that's an sRGB image. Haven't implemented sRGB reader yet so it actually doesn't ever truly load your image. Hopefully I get that part done some time soon, but I'll keep this image and keep trying to load it as I make updates.

- Jeremy

jdarling
15-07-2010, 07:20 PM
@User137:
Well, I was wrong... It had nothing to do with sRGB (oops), it had to do with how I was reading in the IDAT sub-section headers. The problem is fixed now and the pas file is updated. Tried loading your png and now it loads just fine.

Thanks for finding the bug, and hopefully this fix helps you out.

@Everyone:
I did forget to mention that right now I'm only decoding non-interlaced PNG's. Once I get all the sections figured out and the bugs fixed, then I'll start on Interlaced images.

- Jeremy

User137
15-07-2010, 10:55 PM
Both works now, MsPaint and Gimp formats. But they are still upside down ;D Notice that normal perspective view has higher (+ side) Y coordinate at top of screen. My screen is set up on ortho mode this way, so upper left is 0,0:
glViewport(X,Y,_Width,_Height);
...
glMatrixMode(GL_PROJECTION);
glLoadIdentity; gluOrtho2D(X,X+_Width,Y+_Height,Y);
glMatrixMode(GL_MODELVIEW);

Nice work, hope to see it finished.

paul_nicholls
15-07-2010, 11:07 PM
Nice one Jeremy :)

If anyone is interested, there is also now this version of a no 3rd party PNG reader/writer that is open source and works with FPC (written by Christian-W. Budde in the graphics32 forums):

http://sourceforge.net/projects/gr32pnglibrary/

It loads a png file into a TBitmap32 class, but I think the underlying classes could also be used to load png files into other things too instead of just Graphics32 classes :)

cheers,
Paul

jdarling
16-07-2010, 03:33 PM
Both works now, MsPaint and Gimp formats. But they are still upside down ;D Notice that normal perspective view has higher (+ side) Y coordinate at top of screen. My screen is set up on ortho mode this way, so upper left is 0,0:
glViewport(X,Y,_Width,_Height);
...
glMatrixMode(GL_PROJECTION);
glLoadIdentity; gluOrtho2D(X,X+_Width,Y+_Height,Y);
glMatrixMode(GL_MODELVIEW);

Nice work, hope to see it finished.


Ahh.. yes, the scan lines were inverted. Fixed now, same download. Pixel[0,0] is now equal to Data^ and Pixel[width-1, height-1] is now (Data+(width*height)-1)^ (IOTW: Upper left is now 0, 0).

- Jeremy

User137
16-07-2010, 04:09 PM
Yep, it looks correct now.

jdarling
16-07-2010, 05:50 PM
Quick update, I've added Scanline[] and Pixel[x,y] accessors to the class. Not sure if this will be any use to anyone, but just in case they were easy enough to add.

pas file has been updated, but since this is a new page, here is the DL link:
http://www.eonclash.com/gl/uPNGSupport.pas

Also, will be starting a new post about a TGA loader that follows the same model as this one in case anyone is interested.

- Jeremy

Ñuño Martínez
20-07-2010, 11:36 AM
Thanks for this present, jdarling. :-* May be I can hack it and use it with my Allegro.pas wrapper or something.

de_jean_7777
25-07-2010, 10:20 AM
Hey jdarling. Your PNG loader is quite useful. I incorporated your loader into my image library to load PNGs. It works nicely, but there are several problems, which at first I thought were my fault, but I made a simple test program that includes only your loader just to be sure, and it was not my fault ::).

Here's the test program that loads PNGs using your unit:

PROGRAM upngtest;

USES heaptrc, uPNGSupport;

CONST
imgFN = 'vlad.png';

VAR
p: TRawPNG = nil;
fn: string = imgFN;

BEGIN
writeln('upngtest');
writeln();

if(ParamCount > 0) then
fn := ParamStr(1);

writeln('Loading ', fn);
p := TRawPNG.Create(fn);
if(p.LoadError = 0) then begin
writeln('Loaded image: ', fn)
end else
writeln('Error: ', p.LoadError, '|', p.LoadErrorMessage);

p.Free();
writeln('Done');
END.


In the above program, heaptrc will make the loader report strange errors, while without heaptrc it will load the PNGs normally.

Another thing is a range check error:


An unhandled exception occurred at $00412E5F :
ERangeError : Range check error
$00412E5F TRAWPNG__FILTERROW, line 644 of C:/Programming/FPC_PP/Projects/dIm
age/Units/uPNGSupport.pas
$004127EF TRAWPNG__DECODENONINTERLACED, line 564 of C:/Programming/FPC_PP/Pr
ojects/dImage/Units/uPNGSupport.pas
$00411AD5 TRAWPNG__READIDAT, line 345 of C:/Programming/FPC_PP/Projects/dIma
ge/Units/uPNGSupport.pas
$00414036 TRAWPNG__LOADFROMSTREAM, line 837 of C:/Programming/FPC_PP/Project
s/dImage/Units/uPNGSupport.pas
$00413AB9 TRAWPNG__CREATE, line 771 of C:/Programming/FPC_PP/Projects/dImage
/Units/uPNGSupport.pas
$0040163A main, line 20 of upngtest.pp


This range check error causes crashing when loading a certain PNG, but when the check is disabled that same PNG loads OK. The particular PNG I've used is named vlad.png from your SDL, Lua and FPC game demo which I downloaded from your site. Or for easier access I've uploaded it to imagebin (http://imagebin.org/106680).

I suspect you've got memory corruption somewhere in your code, but could be something else.

jdarling
26-07-2010, 12:45 PM
Yeah, I've noticed that you can't use HeapTrace or any of the other stuff to "debug" the PNG's that are loaded. While there may be a leak or an overrun/override I can't find it. Running the code in a VM and monitoring actual memory usage there are no leaks found (same amount of free memory before as after the run), so I'm a bit lost.

Anyone finds something I'd love to hear it. The "errors" thrown move depending on machine and version you compile in, so this points more to a Memory Overrun than it does a typical leak.

- Jeremy

noeska
26-07-2010, 04:24 PM
Does it support the alpha channel also? Great for making imposters (e.g. flat trees, grass).

jdarling
26-07-2010, 06:09 PM
Does it support the alpha channel also? Great for making imposters (e.g. flat trees, grass).


Full alpha loading and editing. Actually the "data" pointer is a pointer to an array of ARGB (or TColor as defined in the unit), so before you "bind" or use the loaded image you can do whatever you want.

paul_nicholls
11-08-2011, 12:25 PM
Hi Jeremy,
I know this is over a year old now, but I was wondering if your PNG loader requires a .dll (or .so) at all? Doesn't paszlib use a dll?

I am after a unit that can load a PNG image that doesn't require any dlls, etc. so I can use it under Windows/Linux, and possible Mac OSX down the track :)

If paszlib needs a dll then I might try converting Christian-W. Budde's PNG loader that works with Graphics32 so it just works with a 32-Bit pixel buffer of some kind and has no graphics32 dependencies at all either...

cheers,
Paul

VilleK
11-08-2011, 12:46 PM
I haven't seen this thread before, I might have use for this too!

Also if anyone needs to embed cross-platform jpeg-support there is the excellent NanoJpeg implementation (http://keyj.emphy.de/nanojpeg/), and my Pascal port (http://www.emix8.org/static.php?page=nanoJpeg) of it.