📜 ⬆️ ⬇️

Graphic format JNG - what is useful, how it works, what to convert, view and download

The picture shows a Turkish sniper rifle with a very suitable name - JNG. In the article, as you have already guessed, it will be a question of the JNG graphic format, and not about weapons at all. On Habré already flashed topics relating to this format, but there were not many, and some, unfortunately, were subsequently deleted by the authors. Despite the fact that JNG is not a very popular format and is based on the MNG format, which, apparently, can be considered stillborn, JNG has one very good feature - this is a high compression ratio of lossy graphics plus alpha channel.
In essence, JNG is a subtype of the MNG format (however, with its own marker in the header, allowing to distinguish both of these formats). Color data is saved in JPEG format, but the alpha can be stored in one of two options - either compressed using JPEG as a grayscale image or using the same as in PNG - lossless compression.
Where can JNG come in handy? For me, it was most suitable for storing textural atlases in mobile games. A small example - the original graphics set from the game weighed 57 megabytes, after replacing all the png with jng - the graphics set began to weigh 15 megabytes. Not a bad win for a mobile game. Searching other areas where jng can be applied will be left to the reader, I will describe what to watch, how to create and how to load (with examples of C / C ++ code) pictures in JNG format, as well as a bit of theory about its structure.



How to view JNG pictures?


- XnView (http://www.xnview.com/) - allows you to open JNG after installing the plugin.
- IrfanView (http://www.irfanview.com/) - also requires a plugin (comes in a package with a bunch of other formats).
')

How to save graphics in JNG format?


Firstly there is a plug-in for Photoshop - download.fyxm.net/JNG-Format-8069.html . It looks like it doesn’t really develop, but nevertheless it does its job.
Secondly, you can use a fairly well-known open source utility - ImageMagic (http://www.imagemagick.org)
Understanding the JNG format, I wrote a simple converter that allows you to convert png to jng - code.google.com/p/png2jng/downloads/list
(The converter is built under windows, but after a short file that deals with the replacement of functions from <tchar.h>, it can be built for any other platform).
Of the minuses of my program:
- I didn’t finish 16 bit png / jng (I didn’t need to support such a bit depth);
- filtering of alpha channel data before compression is not used. Potentially adding support for PNG filters can achieve a better compression rate of the alpha channel.

How to download JNG?


libmng

Use libmng sourceforge.net/projects/libmng
Pros:
- fairly dovno;
- can load not only jng but also animated mng;
- there is also code to create mng / jng images.
Minuses:
- large and heavy library
- pretty confusing API for loading graphics, designed for animation;
- the inability to read pictures in rows;
- quite gluttonous from memory.
Also libmng requires jpeglib and zlib.
If you decide to use libmng - here is a small example of loading graphics with it.
You can find the entire source code of the example here - code.google.com/p/png2jng/source/browse/trunk/jngdump/jngdump.cpp (as part of png2jng)
Here I will describe the key points.

First of all, you need to initialize the library and configure the required minimum callbacks.
For the convenience of downloading a picture, I have defined such a structure, which I pass through the user parameter to callbacks:

struct mng_info { FILE* file;//    mng_uint32 width; mng_uint32 height; mng_uint32 timer; unsigned char* pixels; }; 


So - libmng initialization

 /*        ,     mng_handle.          ,       .   – libmng   ,       ,         (  –  calloc,   memset   ). */ mng_handle hmng; mng_info mngInfo; mng_retcode retCode; hmng = mng_initialize(&mngInfo, mymngalloc, mymngfree, MNG_NULL); /*  */ /* */ Mng_setcb_errorproc(hmng, mymngerror); /*     .   ,     .*/ mng_setcb_openstream(hmng, mymngopenstream); /* */ mng_setcb_closestream(hmng, mymngclosestream); /* */ mng_setcb_readdata(hmng, mymngreadstream); /*  –         ,  ,     */ mng_setcb_processheader(hmng, mymngprocessheader); /*   libmng        */ mng_setcb_getcanvasline(hmng, mymnggetcanvasline); /*  .   ,        ,   .      – . */ mng_setcb_gettickcount(hmng, mymnggettickcount); /* .   ,      . */ mng_setcb_settimer(hmng, mymngsettimer); /*  libmng  ,      .        ,     getcanvasline ,       .*/ mng_setcb_refresh(hmng, mymngimagerefresh); 


Of the callbacks I will cite here only those that are of the greatest interest.

 /*   */ static mng_bool mymngreadstream(mng_handle mng, mng_ptr buffer, mng_uint32 size, mng_uint32 *bytesread) { mng_info *mymng = (mng_info*)mng_get_userdata(mng); *bytesread = fread(buffer, 1, size, mymng->file); return MNG_TRUE; } mng_bool mymngprocessheader (mng_handle hHandle, mng_uint32 iWidth, mng_uint32 iHeight) { mng_info* mngInfo = (mng_info*)mng_get_userdata (hHandle); /*   */ if (mng_set_canvasstyle (hHandle, MNG_CANVAS_RGBA8)) { /* */ return MNG_FALSE; } mngInfo->width = iWidth; mngInfo->height = iHeight; mngInfo->image = new unsigned char[iWidth * 4 * iHeight];//    return MNG_TRUE; } mng_ptr mymnggetcanvasline(mng_handle hHandle, mng_uint32 iLinenr) { mng_info* mngInfo = (mng_info*)mng_get_userdata (hHandle); /*    */ return mngInfo->image + mngInfo-> width * 4 * iLinenr; } 


Loading pictures.

 /*        .*/ /*        mng */ retCode = mng_read(hmng); /* –     JNG.         MNG*/ if(mng_get_sigtype (hmng) != mng_it_jng) { /* –   */ } 


Now we actually need to get the decoded pixels.
Until now, everything was somewhat reminiscent of such libraries as libng or jpeglib.
Further, there is a cardinal difference, since libmng was originally designed for animated pictures.

 /*  mng_display_resume  libmng      */ retCode = mng_display(hmng); while((retCode == MNG_NEEDTIMERWAIT) && (mngInfo.timer <= 1)) { retCode = mng_display_resume(hmng); } /*   –     pixels   mng_info*/ 


Then it remains only to clear the resources allocated to libmng.

 mng_cleanup(&hmng); 


Well, do not forget to close our threads, free up memory, etc.

As you can see, nothing complicated. However, in my case, one feature of libmng turned out to be fatal, and made me take up cycling.
The fact is that when decoding a picture, libmng inside allocates a buffer equal to the width * bpp * height. In my, I had to load the texture 2048x2048 in 5551 format. Libmng itself does not support this format. It turns out that I also had to allocate a buffer of 2048 * 2048 * 4, load into it, and then distill into a two-byte format. It turned out the overhead of 32 megabytes (plus the weight of the image file itself, because it comes from the archive)! At some point, the game just ran out of memory. Picked up the very tangled insides of libmng, I realized that I couldn’t overcome this problem with a swoop and decided to look away - but couldn’t I upload the picture myself without the help of libmng, especially since the format itself didn’t look particularly scary, but there was an article (unfortunately it has already been deleted) where they did a similar trick, but only in a browser.

libjng


So I gave birth to libjng ( code.google.com/p/libjng ) - a simple library that only knows how to download jng. The library is too loud. In fact, it consists of one header file and one file - the source for C.

Pros:
- easy to use API;
- compact - source code with a header file - 74 kilobytes (a bit of a binary);
- allows you to load the alpha channel without changing the bit depth (conveniently when you need to load into formats like 5551);
- and most importantly - allows you to decode a file in rows, like libpng and jpeglib;
- can decode a picture immediately from the buffer, without duplicating data, and it is possible to unload blocks from streams by setting callbacks.
Disadvantages:
- we are not able to output data in 16 bits per channel (although in theory jng will load where JPEG will have 12 bits per sample, or alpha will be 16 bits - should work)
- also dependent on third-party libraries - jpeglib, zlib.

In the place with the library in the repository there is a small example that allows you to load jng and dump the pixels into the tga format (since this is the simplest graphic format).

A brief example of using libjng (a use case when the entire picture is already loaded into memory).

 /*  */ #include "jng_load.h" /*  –  */ void* my_jng_alloc(size_t size) { return malloc(size); } void my_jng_free(void* ptr) { free(ptr); } void my_jng_errorproc(jng_handle handle, int errorCode, unsigned long chunkName, unsigned long chunkSeq, const char* erorText) { printf("JNG error %d! chunk = %d, chunkSeq = %d, text = %s\r\n", errorCode, chunkName, chunkSeq, erorText); } /* imageSize –  ,    imageBuffer –     */ jng_handle handle; handle = jng_create_from_data(NULL, imageBuffer, imageSize, my_jng_alloc, my_jng_free, my_jng_errorproc, JNG_FLAG_CRC_CRITICAL);/*     crc    .*/ if(!handle) { /*    –    ,  .      errorproc */ } /*   .   , ,    .*/ if(!jng_read(handle)) { jng_cleanup(handle); /* */ } /*   */ imageWidth = jng_get_image_width(handle); imageHeight = jng_get_image_height(handle); /*  ,     */ jng_set_out_alpha_channel_bits(handle, JNG_OUT_BITS_8); jng_set_out_color_channel_bits(handle, JNG_OUT_BITS_8); jng_set_out_color_space(handle, JNG_RGB); jng_set_color_jpeg_src_type(handle, JNG_JPEG_SRC_DEFAULT);/*,       8  12  jpeg  – JNG     */ /*  –  ()  jpeg   png,   */ if(!jng_start_decode(handle)) { jng_cleanup(handle); /**/ } /*    */ colorBytesNum = jng_get_out_color_channel_bytes(handle); alphaBytesNum = jng_get_out_alpha_channel_bytes(handle); colorComponentsCount = jng_get_out_color_components_num(handle); if(!colorBytesNum || !colorComponentsCount) { jng_finish_decode(handle); jng_cleanup(handle); /*  ?!*/ } /*   bpp    , ..       * / pixelBytesNum = colorBytesNum * colorComponentsCount + alphaBytesNum; rowPitch = pixelBytesNum * imageWidth; pixelBufferSize = rowPitch * imageHeight;/*    */ /*   . ,  ,     5551       32       –      ,       .         .*/ pixelBuffer = (unsigned char*)malloc(pixelBufferSize); 


Line by line reading of a picture (indeed line by line - from the overhead four or five buffers capable of storing a string for color and for alpha).

 unsigned char* imageLinePtr = (unsigned char*)pixelBuffer; for(currentLine = 0; currentLine < imageHeight; currentLine++) { if(!jng_read_scanline(handle, imageLinePtr)) { /*    */ readRes = -1; break; } imageLinePtr += rowPitch; } /* */ jng_finish_decode(handle); jng_cleanup(handle); /*That's All, Folks!*/ 


In short, how it works.


JNG format, like MNG, consists of a set of chunks (pieces). Each chunk contains a header - 4th byte - data size, 4th byte - type identifier, optional data and at the end of 4th byte - crc. As you can see, subtracting and parsing such a structure is an elementary task.

We need to disassemble such chanki:
JHDR - file backups - describes the image size, compression formats, bit depth, etc.
JDAT - chunk containing the color data of the picture. Regular JPEG (if you block this block in a hex editor into a separate file, then you can perfectly open it in any viewer);
JSEP - shares JDAT chunks if there are eight and twelve JPEG JPEG images in the picture at the same time (only JSEP may appear between such chunks, but not any other data);
JDAA - alpha channel, compressed in JPEG - everything is the same as with JDAT, but there can only be one eight-bit JPEG in shades of gray;
IDAT is an alpha channel packed in the same way as PNG pixels are packed.
IEND - the end of the picture.

Both the color part of the data and the alpha channel can be broken into several chunks. You just need to consistently read them and glue the data.

With the data in jpeg, everything is simple - jpeglib is configured here and then we delete the data line by line through jpeglib. With IDAT is somewhat more complicated. libpng we do not screw, because This is not a full png, but in fact only one of the chunks from png.
The data itself in IDAT are line by line, they are packed with zlib. The string begins with a byte marker, which indicates the type of filtering, and then the data itself. Thus, decoding is reduced to the fact that we unpack the line (zlib inflate), check the first byte, filter (this is quite a volume theme and who is interested - I advise you to look inside pnglib in the file pngrutil.c, the function png_read_filter_row), then if in one byte more than one pixel is packed — unpack them into separate bytes and expand to a byte or word if necessary.
Some filters require the availability of data from the previous line - so the overhead is added to another line. However, filtering is performed not over pixels but over data bytes, so if we have less than eight bits per pixel, then the overhead will be less when unpacking.

Behind this all - I hope the text was interesting and useful for you. In short, I tried to highlight not only the applied side of using JNG but also touched the theory a little, for the sake of curiosity.

Source: https://habr.com/ru/post/155547/


All Articles