PDA

View Full Version : BeRoPNG - A very tiny but complete PNG loader



BeRo
15-07-2011, 08:31 PM
I do want to share my useful BeRoPNG unit here. It's very tiny but complete PNG loader with a own tiny inflate routine, so it has no external unit dependencies. It supports even extended features such as Apple's proprietary extension CgBI and so on.

And here is the link: http://rootserver.rosseaux.net/stuff/BeRoPNG.pas (36 kB src)

It's licensed under the 2-clause simplified BSD license.

So have a lot fun with it :D

Traveler
15-07-2011, 08:37 PM
Thats pretty neat. Thank you for sharing!

code_glitch
15-07-2011, 09:12 PM
Ah yes danke. Whats the license? I'd like to use it in Prometheus to help replace sdl_image (sloow) to go with the new Win32 and X11 code to finish phasing out sdl for video...

BeRo
15-07-2011, 09:26 PM
Ah yes danke. Whats the license? I'd like to use it in Prometheus to help replace sdl_image (sloow) to go with the new Win32 and X11 code to finish phasing out sdl for video...

It's licensed under the 2-clause simplified BSD license.

de_jean_7777
16-07-2011, 12:17 AM
Thanks man. You're awesome! I was missing a nice PNG loader.

WILL
16-07-2011, 12:38 AM
Very cool Benjamin! Any chance of there being a stand-alone PNG screenshot generating unit in the future? :)

virtual
16-07-2011, 08:30 AM
thanks for sharing , i like tiny stuff

by the way , i like your program picatune , are there any chance that i can use it in my demos ? , i have "kb" v2m pascal port , but gives much size than c++ version .

Cybermonkey
16-07-2011, 10:41 AM
Ah yes danke. Whats the license? I'd like to use it in Prometheus to help replace sdl_image (sloow) to go with the new Win32 and X11 code to finish phasing out sdl for video...
What about the Vampyre Imaging Library? It can handle lots of more formats.(JPEG, BMP,GIF etc.) I use it in my EGSL interpreter project and it works pretty well. But of course, if you only need PNG support ...

code_glitch
16-07-2011, 03:36 PM
I'm looking at slimming Prometheus down. From what I've seen Vampyre has a lot features that would never be used, this is literally just a loader by the looks of it and a pretty small one so from that standpoint its perfect. Aside, I already have written up the file type detection system (not the file extension, the binary file headers) so I just need to tell it what proc to use and I'm golden.

BeRo
16-07-2011, 06:41 PM
What about the Vampyre Imaging Library? It can handle lots of more formats.(JPEG, BMP,GIF etc.) I use it in my EGSL interpreter project and it works pretty well. But of course, if you only need PNG support ...

BeRoPNG is designed for small binarysize projects and for embedded projects on embedded devices with limited resources. For example my android game FoembJump (https://market.android.com/details?id=com.bero.games.foembjump) uses BeRoPNG together with OpenGL ES 2.0. I'm demoscener and a Farbrausch member, so I've fondness for small sized stuff :D

BeRo
16-07-2011, 06:48 PM
thanks for sharing , i like tiny stuff

by the way , i like your program picatune , are there any chance that i can use it in my demos ? , i have "kb" v2m pascal port , but gives much size than c++ version .

Yes, if you give me more informations about your demo work. Anyway Picatune2 is released now, http://audio.rosseauxnet.de/software/sequencer/picatune2/ , which was also used in "fr-080: Strobo-plus-32767: Pacemaker" (Youtube (http://www.youtube.com/watch?v=ykL4LxdajfA), Pouet (http://www.pouet.net/prod.php?which=56877)), and yeah fr-080 is a (Object-)Pascal-powered 64k intro, which was compiled with the Delphi 5 compiler with a own tiny system unit from me. :)

paul_nicholls
18-08-2011, 10:49 PM
I do want to share my useful BeRoPNG unit here. It's very tiny but complete PNG loader with a own tiny inflate routine, so it has no external unit dependencies. It supports even extended features such as Apple's proprietary extension CgBI and so on.

And here is the link: http://rootserver.rosseaux.net/stuff/BeRoPNG.pas (36 kB src)

It's licensed under the 2-clause simplified BSD license.

So have a lot fun with it :D

Thanks for sharing BeRo!

Is it ok if I use this in a closed-source DLL for an engine I am writing?

if so, I was wondering, how does one use this?


function LoadPNG(DataPointer:pointer;DataSize:longword;var ImageData:pointer;var ImageWidth,ImageHeight:integer):boolean;

is DataPointer just the start of a PNG file, and DataSize the same as the PNG file size?

cheers,
Paul

paul_nicholls
18-08-2011, 11:28 PM
Woo Hoo! I have now gotten PNG loading working in my engine...thanks BeRo, you rock!! :D

I have attached a zip file containing my png loader, my bmp loader, my unfinished tga loader (needs converting to TFileStream still), and my image buffer unit that my loaders and texturing uses in my engine...

562

Enjoy :)
cheers,
Paul

Traveler
19-08-2011, 07:59 AM
Nice. Thanks for sharing Paul.

paul_nicholls
19-08-2011, 10:24 AM
You're welcome :) I take, and I give LOL ;)

cheers,
Paul

paul_nicholls
19-08-2011, 01:08 PM
Hey all, just so you know, I tried compiling my engine in Delphi 2010 instead of Lazarus, and the BeRo png file doesn't compile due to ansichar and char incompatibilities, and other similar stuff...

So I fixed the compiling errors, but now it fails to load my test png file now due to some unknown issue :(

Summary, it works using Lazarus, but not D2010 at the moment...no crash, just no image.

cheers,
Paul

paul_nicholls
19-08-2011, 01:26 PM
Ok, I have fixed it now and it runs under D2010! yay :)
I also replaced all #0 with AnsiChar(#0)...

565

cheers,
Paul

deathshadow
21-08-2011, 11:34 PM
What about the Vampyre Imaging Library?
... and it's massive (relying on the monstrous zlib library for png decode), slow (often painfully so), and on the whole completely unsuited to many projects because of it.

Take on windows for example, where a game I've been puttering around on making for a year or so just had it's executable size cut by two-thirds just by switching to this.

Could be worse though, could be SDLImage, were you need 200k in two DLL's just to unpack .png

Thanks BeRo -- this looks to be getting a lot of use from me.

I should clean up my BMP loader (with RLE4 and RLE8 support) and upload it sometime... sad part is I just ported my BMP loader to PHP as GD actually lacks BMP support. (crappy little one-bit WBMP is NOT Windows BMP!)

deathshadow
21-08-2011, 11:51 PM
Uhm... one thing...



function Swap32(x:word):word;
begin
result:=(Swap16(x and $ffff) shl 16) or Swap16((x and $ffff0000) shr 16);
end;

That doesn't make any sense. Shouldn't X be longword? Word and $FFFF0000 should always return zero on this. Also, if you are shifting right off a 32 bit integer by 16, there's no reason to do an AND on it.

I'd probably also hardcode the byte swaps -- the overhead of the function calls slows down each of these functions in turn.

Playing with checking ifdef cpui386 to add assembler optimized versions of your various routines...


{$ifdef cpui386}
function Swap16(x:word):word; assembler;
asm
mov ax,x
xchg ah,al { thankfully FPC/Delphi treat AX as the return value }
end;
{$else}
function Swap16(x:word):word;
begin
result:=(x shl 8) + (x shr 8);
end;
{$endif}
(also another case where the "and" is unnecessary due to the bit width and pascal's strict typecasting.)

also playing with making a TP7 back-port as I'm still churning out DOS games (http://www.cutcodedown.com/retroGames/pakuLive/), and PNG support would be really handy there.

paul_nicholls
22-08-2011, 02:13 AM
I just realised that I have a problem :(

I have my LoadPNGFromFile() routine, and I wanted to make a LoadPNGFromStream() version...

The problem is that in the loading code below requires a pointer to a PNG file, and the length!!


function LoadPNGFromFile(FileName: String; Buffer: TImageBuffer32): Boolean;
var
PNGFile : TMemoryStream;
PNGData : Pointer;
PNGWidth : Integer;
PNGHeight: Integer;
PNGAddr : PRGBA;
BufferRow: PRGBAArray;
x,y : Integer;
begin
Result := False;

if not FileExists(FileName) then Exit;

PNGFile := TMemoryStream.Create;
try
PNGFile.LoadFromFile(FileName);
Result := LoadPNG(PNGFile.Memory,PNGFile.Size,PNGData,PNGWid th,PNGHeight);
if not Result then Exit;

Buffer.SetSize(PNGWidth,PNGHeight);

PNGAddr := PNGData;
// copy bitmap pixels to buffer pixels
for y := 0 to PNGHeight - 1 do
begin
BufferRow := Buffer.ScanLine[y];
for x := 0 to PNGWidth - 1 do
begin
BufferRow^[x].r := PNGAddr^.r;
BufferRow^[x].g := PNGAddr^.g;
BufferRow^[x].b := PNGAddr^.b;
BufferRow^[x].a := PNGAddr^.a;
Inc(PNGAddr);
end;
end;
finally
PNGFile.Free;
end;
end;


the loading code in the BeRo file uses the DataSize parameter as an indicator of when the PNG file has been read in, I would like to be able to change this so that the code knows when to stop without the user passing in the size.

Any ideas?

cheers,
Paul

BeRo
23-08-2011, 06:09 AM
function Swap32(x:word):word;
begin
result:=(Swap16(x and $ffff) shl 16) or Swap16((x and $ffff0000) shr 16);
end;



BeRoPNG should be portable and small (even at source code level) at the same time, so that it should containing no or only few inline assembler parts, but a good optimizing compiler with inline-support and good code peephole replace templates should optimize this with "bswap eax" on x86-32 and x86-64 (what Delphi&FPC does it not yet, but some C/C++ compilers. My big hope for this is the LLVM backend for FPC). The "and $ffff0000" is just for that it's more readable, the compiler will optimizing it out, but that is something you should know (as you probably already do).

BeRo
23-08-2011, 06:15 AM
Any ideas?

cheers,
Paul

Just remove DataSize:longword; and the DataEnd checks from BeRoPNG (change "while (pansichar(DataPtr)+11)<pansichar(DataEnd) do begin
" to "while true do begin" and so on, but without this it's a security leak then. :-)

paul_nicholls
23-08-2011, 06:39 AM
Just remove DataSize:longword; and the DataEnd checks from BeRoPNG (change "while (pansichar(DataPtr)+11)<pansichar(DataEnd) do begin
" to "while true do begin" and so on, but without this it's a security leak then. :-)

Thanks :) I think I might just leave it then haha

cheers,
Paul

deathshadow
27-08-2011, 08:21 PM
Still, you're swap32 function should be longword, not word, no?

word and $FFFF0000 === 0 -- ALWAYS
word shl 16 === 0 -- ALWAYS.

So in effect:

result:=(Swap16(x and $ffff) shl 16) or Swap16((x and $ffff0000) shr 16);

is the same thing as saying

result:=0;

Which means on big-endian systems when your swap function is called, you'll get nothing but zero.

Also, you're sending zero to your swap16 function...



function Swap32(x:longword):longword;
begin
result:=(Swap16(x and $ffff) shl 16) or Swap16(x shr 16);
end;


Is what you should have there. What you have right now won't work... Thankfully it's only called on big endian systems -- and you've probably only tested little endian.

It may also need typecasting forced, since swap16 returns word, not longword meaning that shl 16 could STILL result in zero.

BeRo
31-08-2011, 09:41 AM
word and $FFFF0000 === 0 -- ALWAYS
word shl 16 === 0 -- ALWAYS.


Sorry, you're wrong. See my code again:



function Swap32(x:word):word;
begin
result:=(Swap16(x and $ffff) shl 16) or Swap16((x and $ffff0000) shr 16);
end;


The first swap16 call gets "x and $ffff", and the result of it will shifted 16 bits to left, which's correct.
The second swap16 call gets "(x and $ffff0000) shr 16" or short "x shr 16", and the result of it will not shifted because it was already shifted 16 bits to right, which's also correct.

I guess, you had simply overlook the correct nested levels of the (...) blocks in my code.

deathshadow
25-09-2011, 12:02 PM
Forgot about this thread... but:

function Swap32(x:word):word;

word is 16 bit unsigned... can only contain 0..$FFFF -- therein:

x and $ffff returns X

x and $ffff0000 returns 0, always...

since $FFFF is the highest number WORD can contain, $0000FFFF and $FFFF0000 == 0, ALWAYS. X in your function CANNOT hold a value that would return anything but zero on that second AND.... EVER.

See what I'm saying? for that function to work X would have to be DWORD and/or Longint... 32 bit... not 16 bit which is what WORD is.

BeRo
27-09-2011, 08:40 AM
See what I'm saying? for that function to work X would have to be DWORD and/or Longint... 32 bit... not 16 bit which is what WORD is.

Yeah, I know (see the rest of the code), but this is unimportant, since this is 1. just a typo error in the typedef in the functiondef paramdef header (which's quick fixable) and 2. unused on little-endian systems

BeRo
14-05-2013, 11:50 AM
I've uploaded a new pure pascal version under the same url http://rootserver.rosseaux.net/stuff/BeRoPNG.pas , which's a bit faster and also compatible to Delphi XE3 & XE4 now.

And the other http://rootserver.rosseaux.net/stuff/BeRoPNGAndroidSpecial.pas variant fallbacks to cpng (a wrapper unit to the on ARM Android faster c-based libpng implementation, which's a bit for Android's own libpng.so modified png.pas from FPC, so you do need http://rootserver.rosseaux.net/stuff/cpng.pas ), if you're building for the Android target, otherwise the pure pascal code part will be used, if you're building for other targets than Android.

imcold
29-05-2013, 03:57 PM
I tried the pure pas version in freepascal, works nicely :) I'd add a comment that ImageData is a PPNGPixel for clarity, not that it mattered much.

User137
08-06-2013, 02:22 PM
I feintly remembered reading long time ago, that FPC PNG would have some non-free licence. But now that i tried to find information, i found nothing. All FPC graphics seem to be of same modifiedLGPL that all the other code. But is FPC implementation heavier than this? Maybe it's coming at the cost of supporting more formats, i don't know. I went through all PNG related source files i could find, and there were none licence differences.

phibermon
02-07-2013, 05:23 PM
I think that while the full blown libs might support more image types, something like BeroPNG is a far better choice in order to avoid library dependencies, you're going to to be able to load 9x% of the PNGs you find using it and you're defining your own content usually.

There might be some differences in speed but it's negligable, only the most chuggy old iphones are going to show much difference between some native arm lib or arm compiled bero.

Cybermonkey
22-05-2014, 08:28 PM
Sorry, I know this is an old thread but I tried BeRoPNG to work with ptcgraph. This is my (simple) example:

var
f: file;
fsize: longword;
buffer: pbyte;
ImageData: PPNGPixel;
ImageWidth,
ImageHeight:longint;
gd,gm:smallint;
BEGIN
AssignFile(f, 'bluepointer.png');
Reset(f, 1);
fsize := FileSize(f);
buffer := getmem(fsize);
blockread(f, buffer^, fsize);
CloseFile(f);

BeRoPNG.LoadPNG(buffer, fsize, ImageData, ImageWidth, ImageHeight, false);

Gd:=D16bit;
Gm:=m800x600;
InitGraph(Gd, Gm, 'Test');


if GraphResult <> grOk then begin
Writeln('Graphics error: ', GraphErrorMsg(ErrorCode));
Halt(1);
end;

putimage (10,10,imageData^,1);
delay (2000);
closegraph;


freemem(buffer);
freemem(ImageData);


END.

But the result isn't correct (see the attached screenshot). I think the problem is that ptcgraph uses a 16 bit colour format with no alpha channel. Any ideas how this can work?
1286

code_glitch
23-05-2014, 11:58 AM
Cybermonkey: I've run into this myself quite a few times. Try open the image in gimp or something and fiddle a bit with the compression options when you export. This usually fixes things for reasons I don't fully understand...

(Though reading your suspicion I guess it might be because GIMP adds an alpha channel during the export? :D)

Cybermonkey
23-05-2014, 03:59 PM
Unfortunately it still doesn't work ...:(