csPtr<iDataBuffer> csJNGImageIO::Save (iImage *Image, iImageIO::FileFormatDescription *, const char* extraoptions) { // we need to get a RGB/RGBA version of the image. switch (Image->GetFormat() & CS_IMGFMT_MASK) { case CS_IMGFMT_PALETTED8: //imgRGBA = Image->Clone (); //imgRGBA->SetFormat (CS_IMGFMT_TRUECOLOR | (Image->GetFormat() & CS_IMGFMT_ALPHA)); // act like JPEG plugin; reject paletted image so no // unwanted/unnoticed conversions take place. return 0; break; case CS_IMGFMT_TRUECOLOR: imgRGBA = csRef<iImage>(Image); break; default: // unknown format return 0; } // compression options int quality = 80; bool progressive = false; bool alpha_jpeg = false; int alpha_png_compress = 6; int alpha_jpeg_quality = -1; /* parse output options. options are a comma-separated list and can be either 'option' or 'option=value'. supported options: compress=# image color compression, 0..100 higher values give smaller files but uglier results. progressive progressive encoding. jng_lossy_alpha use lossy JPEG compression for alpha channel (instead of default lossles PNG) jng_alpha_compress alpha channel compression, 0..100 Impact of higher value depends on alpha channel type. JPEG - smaller files, uglier results. PNG - smaller files, longer time to encode. Note: defaults to value for image color compression if lossy alpha is selected. examples: compress=50 progressive,compress=30 */ csImageLoaderOptionsParser optparser (extraoptions); optparser.GetBool ("progressive", progressive); if (optparser.GetInt ("compress", quality)) { quality = 100 - quality; if (quality < 0) quality = 0; if (quality > 100) quality = 100; if (alpha_jpeg_quality == -1) alpha_jpeg_quality = quality; } if (optparser.GetBool ("jng_lossy_alpha", alpha_jpeg)) { if (alpha_jpeg_quality == -1) alpha_jpeg_quality = quality; } if (optparser.GetInt ("jng_alpha_compress", alpha_png_compress)) { alpha_jpeg_quality = 100 - alpha_png_compress; if (alpha_jpeg_quality < 0) alpha_jpeg_quality = 0; if (alpha_jpeg_quality > 100) alpha_jpeg_quality = 100; alpha_png_compress /= 10; if (alpha_png_compress < 0) alpha_png_compress = 0; if (alpha_png_compress > 9) alpha_png_compress = 9; } mng_handle handle = mng_initialize ( mng_ptr(this), cb_alloc, cb_free, MNG_NULL); if (!handle) { Report (object_reg, CS_REPORTER_SEVERITY_WARNING, "failed to initialize libmng"); return 0; } if ((mng_setcb_openstream (handle, cb_openstream) != MNG_NOERROR) || (mng_setcb_closestream (handle, cb_closestream) != MNG_NOERROR) || (mng_setcb_writedata (handle, cb_writedata) != MNG_NOERROR)) { ReportLibmngError (object_reg, handle, "failed to set libmng callbacks"); mng_cleanup (&handle); return 0; } outfile = new csMemFile (); if (mng_create (handle) != MNG_NOERROR) { ReportLibmngError (object_reg, handle, "failed to create new jng"); mng_cleanup (&handle); delete outfile; imgRGBA = 0; return 0; } bool has_alpha = (imgRGBA->GetFormat() & CS_IMGFMT_ALPHA) != 0; if (mng_putchunk_jhdr (handle, imgRGBA->GetWidth(), imgRGBA->GetHeight(), has_alpha ? MNG_COLORTYPE_JPEGCOLORA : MNG_COLORTYPE_JPEGCOLOR, MNG_BITDEPTH_JPEG8, MNG_COMPRESSION_BASELINEJPEG, progressive ? MNG_INTERLACE_PROGRESSIVE : MNG_INTERLACE_SEQUENTIAL, has_alpha?8:0, has_alpha?(alpha_jpeg?8:0):0, 0, 0) != MNG_NOERROR) { ReportLibmngError (object_reg, handle, "failed to put JHDR chunk"); mng_cleanup (&handle); delete outfile; imgRGBA = 0; return 0; } // @@@ chunk data generation. // lots of stuff needs to be done manually. // should be changed as libmng evolves. // write out alpha channel if (has_alpha) { // extract the alpha channel from the image int pixels = imgRGBA->GetWidth() * imgRGBA->GetHeight(); uint8 *alpha = new uint8 [pixels]; uint8 *alphaptr = alpha; csRGBpixel *imgdata = (csRGBpixel*)imgRGBA->GetImageData(); while (pixels>0) { *alphaptr++ = (imgdata++)->alpha; pixels--; } if (alpha_jpeg) { // compress the alpha data as JPEG and write it out. uint8* volatile row = 0; struct jpg_datastore ds; struct jpeg_compress_struct cinfo; struct my_error_mgr jerr; cinfo.err = jpeg_std_error (&jerr.pub); jerr.pub.error_exit = my_error_exit; if (setjmp (jerr.setjmp_buffer)) { Report (object_reg, CS_REPORTER_SEVERITY_WARNING, "failed to JPEG compress alpha data"); mng_cleanup (&handle); delete outfile; delete [] row; delete[] alpha; jpeg_destroy_compress (&cinfo); imgRGBA = 0; return 0; } jpeg_create_compress (&cinfo); jpeg_buffer_dest (&cinfo, &ds); cinfo.image_width = imgRGBA->GetWidth (); cinfo.image_height = imgRGBA->GetHeight (); cinfo.input_components = 1; cinfo.in_color_space = JCS_GRAYSCALE; row = new uint8[cinfo.image_width]; jpeg_set_defaults (&cinfo); jpeg_set_quality (&cinfo, alpha_jpeg_quality, true); jpeg_start_compress (&cinfo, true); JSAMPROW row_pointer[1]; uint8 *image = alpha; row_pointer[0] = (JSAMPLE*)&row[0]; while (cinfo.next_scanline < cinfo.image_height) { for (size_t i=0; i < cinfo.image_width; i++) row[i] = image[cinfo.next_scanline * cinfo.image_width + i]; jpeg_write_scanlines (&cinfo, row_pointer, 1); } jpeg_finish_compress (&cinfo); jpeg_destroy_compress (&cinfo); delete [] row; // funny, mng_putchunk_jdaa is missing from libmng //if (mng_putchunk_jdaa (handle, ds.len, ds.data) != MNG_NOERROR) if (mng_putchunk_unknown (handle, MNG_UINT_JDAA, (mng_uint32)ds.len, ds.data) != MNG_NOERROR) { ReportLibmngError (object_reg, handle, "failed to put JDAA chunk"); mng_cleanup (&handle); delete outfile; delete[] alpha; imgRGBA = 0; return 0; } } else { // generate the IDAT chunk data // we use the "Up" filter. uint8* chunkdata = new uint8[(imgRGBA->GetWidth() + 1) * imgRGBA->GetHeight()]; uint8* lastline = new uint8[imgRGBA->GetWidth()]; uint8* chunkptr = chunkdata; alphaptr = alpha; memset (lastline, 0, imgRGBA->GetWidth()); int lines = imgRGBA->GetHeight(); while (lines > 0) { *chunkptr++ = 2; int pix = 0; while (pix<imgRGBA->GetWidth()) { *chunkptr++ = *alphaptr - lastline[pix]; lastline[pix] = *alphaptr++; pix++; } lines--; } delete[] lastline; // now compress the data z_stream zs; zs.zalloc = (alloc_func) 0; zs.zfree = (free_func) 0; zs.next_in = (Byte *) chunkdata; zs.avail_in = (imgRGBA->GetWidth() + 1) * imgRGBA->GetHeight(); if (deflateInit (&zs, alpha_png_compress) != Z_OK) { Report (object_reg, CS_REPORTER_SEVERITY_WARNING, "deflateInit() failed"); mng_cleanup (&handle); delete outfile; delete[] chunkdata; delete[] alpha; imgRGBA = 0; return 0; } char buff[0x8000]; while (1) { zs.next_out = (Byte *)buff; zs.avail_out = sizeof (buff); int rc = deflate (&zs, Z_FINISH); /* Do actual compression */ size_t size = sizeof (buff) - zs.avail_out; // create a chuk w/compressed data. if (mng_putchunk_idat (handle, (mng_uint32)size, &buff) != MNG_NOERROR) { ReportLibmngError (object_reg, handle, "failed to put IDAT chunk"); deflateEnd (&zs); mng_cleanup (&handle); delete outfile; delete[] chunkdata; delete[] alpha; imgRGBA = 0; return 0; } if (rc == Z_STREAM_END) break; /* finished */ } deflateEnd (&zs); delete[] chunkdata; } delete[] alpha; } // compress the color data as JPEG and write it out. csRGBcolor* volatile row = 0; struct jpg_datastore ds; struct jpeg_compress_struct cinfo; struct my_error_mgr jerr; cinfo.err = jpeg_std_error (&jerr.pub); jerr.pub.error_exit = my_error_exit; if (setjmp (jerr.setjmp_buffer)) { Report (object_reg, CS_REPORTER_SEVERITY_WARNING, "failed to JPEG compress color data"); mng_cleanup (&handle); delete outfile; delete [] row; jpeg_destroy_compress (&cinfo); imgRGBA = 0; return 0; } jpeg_create_compress (&cinfo); jpeg_buffer_dest (&cinfo, &ds); cinfo.image_width = imgRGBA->GetWidth (); cinfo.image_height = imgRGBA->GetHeight (); cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; row = new csRGBcolor[cinfo.image_width]; jpeg_set_defaults (&cinfo); jpeg_set_quality (&cinfo, quality, true); if (progressive) jpeg_simple_progression (&cinfo); jpeg_start_compress (&cinfo, true); JSAMPROW row_pointer[1]; JSAMPLE *image = (JSAMPLE*)csPackRGB::PackRGBpixelToRGB ((csRGBpixel*)Image->GetImageData (), Image->GetWidth () * Image->GetHeight ()); row_pointer[0] = (JSAMPLE*)&row[0]; while (cinfo.next_scanline < cinfo.image_height) { row_pointer[0] = (JSAMPLE*)&image[cinfo.next_scanline * cinfo.image_width * 3]; jpeg_write_scanlines (&cinfo, row_pointer, 1); } jpeg_finish_compress (&cinfo); jpeg_destroy_compress (&cinfo); delete [] row; if (mng_putchunk_jdat (handle, (mng_uint32)ds.len, ds.data) != MNG_NOERROR) { ReportLibmngError (object_reg, handle, "failed to put JDAT chunk"); mng_cleanup (&handle); delete outfile; imgRGBA = 0; return 0; } imgRGBA = 0; if (mng_putchunk_iend (handle) != MNG_NOERROR) { ReportLibmngError (object_reg, handle, "failed to put IEND chunk"); mng_cleanup (&handle); delete outfile; return 0; } if (mng_write (handle) != MNG_NOERROR) { ReportLibmngError (object_reg, handle, "failed to write out JNG data"); mng_cleanup (&handle); delete outfile; return 0; } mng_cleanup (&handle); csRef<iDataBuffer> db (outfile->GetAllData ()); delete outfile; return csPtr<iDataBuffer> (db); }
int fixit (char * zFilenameI, char * zFilenameO) { userdatap pMydata; mng_retcode iRC; /* get a data buffer */ pMydata = (userdatap)calloc (1, sizeof (userdata)); if (pMydata == NULL) /* oke ? */ { fprintf (stderr, "Cannot allocate a data buffer.\n"); return 1; } pMydata->hFileO = 0; /* initialize some stuff! */ pMydata->hHandleI = MNG_NULL; pMydata->hHandleO = MNG_NULL; pMydata->bHasSAVE = MNG_FALSE; pMydata->bHasTERM = MNG_FALSE; pMydata->bIsJASC = MNG_TRUE; pMydata->iLastchunk = MNG_UINT_HUH; pMydata->iTermaction = 0; pMydata->iIteraction = 0; pMydata->iDelay = 0; pMydata->iItermax = 0; /* can we open the input file ? */ if ((pMydata->hFileI = fopen (zFilenameI, "rb")) == NULL) { /* error out if we can't */ fprintf (stderr, "Cannot open input file %s.\n", zFilenameI); return 1; } /* let's initialize the library */ pMydata->hHandleI = mng_initialize ((mng_ptr)pMydata, myalloc, myfree, MNG_NULL); if (!pMydata->hHandleI) /* did that work out ? */ { fprintf (stderr, "Cannot initialize libmng.\n"); iRC = 1; } else { /* some informatory messages */ fprintf (stderr, "Compiled with libmng %s.\n", MNG_VERSION_TEXT); fprintf (stderr, "Running with libmng %s.\n", mng_version_text()); /* setup callbacks */ if ( ((iRC = mng_setcb_openstream (pMydata->hHandleI, myopenstream )) != 0) || ((iRC = mng_setcb_closestream (pMydata->hHandleI, myclosestream )) != 0) || ((iRC = mng_setcb_readdata (pMydata->hHandleI, myreaddata )) != 0) || ((iRC = mng_setcb_errorproc (pMydata->hHandleI, myprocesserror)) != 0) ) fprintf (stderr, "Cannot set callbacks for libmng.\n"); else { /* reaad the file into memory */ if ((iRC = mng_read (pMydata->hHandleI)) != 0) fprintf (stderr, "Cannot read the input file.\n"); else { /* run through the chunk list to get TERM */ if ((iRC = mng_iterate_chunks (pMydata->hHandleI, 0, myiterchunk)) != 0) fprintf (stderr, "Cannot iterate the chunks.\n"); else { if (pMydata->iError) /* did the iteration fail somehow ? */ iRC = pMydata->iError; else { /* can we open the output file ? */ if ((pMydata->hFileO = fopen (zFilenameO, "wb")) == NULL) { /* error out if we can't */ fprintf (stderr, "Cannot open output file %s.\n", zFilenameO); iRC = 1; } else { /* let's initialize the library */ pMydata->hHandleO = mng_initialize ((mng_ptr)pMydata, myalloc, myfree, MNG_NULL); if (!pMydata->hHandleO) /* did that work out ? */ { fprintf (stderr, "Cannot initialize libmng.\n"); iRC = 1; } else { /* setup callbacks */ if ( ((iRC = mng_setcb_openstream (pMydata->hHandleO, myopenstream )) != 0) || ((iRC = mng_setcb_closestream (pMydata->hHandleO, myclosestream)) != 0) || ((iRC = mng_setcb_writedata (pMydata->hHandleO, mywritedata )) != 0) ) fprintf (stderr, "Cannot set callbacks for libmng.\n"); else { if ((iRC = mng_create (pMydata->hHandleO)) != 0) fprintf (stderr, "Cannot create a new MNG.\n"); else { /* run through the chunk again and create the new file */ if ((iRC = mng_iterate_chunks (pMydata->hHandleI, 0, myiterchunk)) != 0) fprintf (stderr, "Cannot iterate the chunks.\n"); else { /* did the iteration fail somehow ? */ if (pMydata->iError) iRC = pMydata->iError; else { /* now write the created new file !! */ if ((iRC = mng_write (pMydata->hHandleO)) != 0) fprintf (stderr, "Cannot write the output file.\n"); } } } } /* cleanup the library */ mng_cleanup (&pMydata->hHandleO); } /* cleanup output file */ fclose (pMydata->hFileO); } } } } } mng_cleanup (&pMydata->hHandleI); /* cleanup the library */ } fclose (pMydata->hFileI); /* cleanup input file and userdata */ free (pMydata); return iRC; }
static bool sWriteMNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel) { TORQUE_UNUSED( bitmap ); TORQUE_UNUSED( stream ); TORQUE_UNUSED( compressionLevel ); return false; #if 0 // ONLY RGB bitmap writing supported at this time! AssertFatal(getFormat() == GFXFormatR8G8B8 || getFormat() == GFXFormatR8G8B8A8 || getFormat() == GFXFormatA8, "GBitmap::writeMNG: ONLY RGB bitmap writing supported at this time."); if(getFormat() != GFXFormatR8G8B8 && getFormat() != GFXFormatR8G8B8A8 && getFormat() != GFXFormatA8) return (false); // maximum image size allowed #define MAX_HEIGHT 4096 if(getHeight() >= MAX_HEIGHT) return false; mngstuff mnginfo; dMemset(&mnginfo, 0, sizeof(mngstuff)); mng_handle mng = mng_initialize(&mnginfo, mngMallocFn, mngFreeFn, MNG_NULL); if(mng == NULL) { return false; } // setup the callbacks mng_setcb_openstream(mng, mngOpenDataFn); mng_setcb_closestream(mng, mngCloseDataFn); mng_setcb_writedata(mng, mngWriteDataFn); // create the file in memory mng_create(mng); mng_putchunk_defi(mng, 0, 0, 0, MNG_FALSE, 0, 0, MNG_FALSE, 0, getWidth(), 0, getHeight()); mnginfo.image = (GBitmap*)this; mnginfo.stream = &stream; switch(getFormat()) { case GFXFormatA8: mng_putchunk_ihdr(mng, getWidth(), getHeight(), MNG_BITDEPTH_8, MNG_COLORTYPE_GRAY, MNG_COMPRESSION_DEFLATE, MNG_FILTER_ADAPTIVE, MNG_INTERLACE_NONE); // not implemented in lib yet //mng_putimgdata_ihdr(mng, getWidth(), getHeight(), // MNG_COLORTYPE_GRAY, // MNG_BITDEPTH_8, // MNG_COMPRESSION_DEFLATE, // MNG_FILTER_ADAPTIVE, // MNG_INTERLACE_NONE, // MNG_CANVAS_GRAY8, mngCanvasLineFn); break; case GFXFormatR8G8B8: mng_putchunk_ihdr(mng, getWidth(), getHeight(), MNG_BITDEPTH_8, MNG_COLORTYPE_RGB, MNG_COMPRESSION_DEFLATE, MNG_FILTER_ADAPTIVE, MNG_INTERLACE_NONE); // not implemented in lib yet //mng_putimgdata_ihdr(mng, getWidth(), getHeight(), // MNG_COLORTYPE_RGB, // MNG_BITDEPTH_8, // MNG_COMPRESSION_DEFLATE, // MNG_FILTER_ADAPTIVE, // MNG_INTERLACE_NONE, // MNG_CANVAS_RGB8, mngCanvasLineFn); break; case GFXFormatR8G8B8A8: mng_putchunk_ihdr(mng, getWidth(), getHeight(), MNG_BITDEPTH_8, MNG_COLORTYPE_RGBA, MNG_COMPRESSION_DEFLATE, MNG_FILTER_ADAPTIVE, MNG_INTERLACE_NONE); // not implemented in lib yet //mng_putimgdata_ihdr(mng, getWidth(), getHeight(), // MNG_COLORTYPE_RGBA, // MNG_BITDEPTH_8, // MNG_COMPRESSION_DEFLATE, // MNG_FILTER_ADAPTIVE, // MNG_INTERLACE_NONE, // MNG_CANVAS_RGBA8, mngCanvasLineFn); break; } // below is a hack until libmng is mature enough to handle this itself //----------------------------------------------------------------------------- U8 *tmpbuffer = new U8[this->byteSize + getHeight()]; if(tmpbuffer == 0) { mng_cleanup(&mng); return false; } // transfer data, add filterbyte U32 effwdt = getWidth() * this->bytesPerPixel; for(U32 Row = 0; Row < getHeight(); Row++) { // first Byte in each scanline is filterbyte: currently 0 -> no filter tmpbuffer[Row * (effwdt + 1)] = 0; // copy the scanline dMemcpy(tmpbuffer + Row * (effwdt + 1) + 1, getAddress(0, Row), effwdt); } // compress data with zlib U8 *dstbuffer = new U8[this->byteSize + getHeight()]; if(dstbuffer == 0) { delete [] tmpbuffer; mng_cleanup(&mng); return false; } U32 dstbufferSize = this->byteSize + getHeight(); if(Z_OK != compress2((Bytef*)dstbuffer,(uLongf*)&dstbufferSize, (const Bytef*)tmpbuffer, dstbufferSize, 9)) { delete [] tmpbuffer; delete [] dstbuffer; mng_cleanup(&mng); return false; } mng_putchunk_idat(mng, dstbufferSize, (mng_ptr*)dstbuffer); //----------------------------------------------------------------------------- mng_putchunk_iend(mng); delete [] tmpbuffer; delete [] dstbuffer; mng_write(mng); mng_cleanup(&mng); return true; #endif }
/** * @brief write buffered frames to MNG file * @return 0 on success, 1 on error */ static int vomng_write_file(void) { FILE *file; mng_handle mng; struct vomng_frame *frame; unsigned int frames, duration_ms; int first; /* refuse to create empty MNG file */ if (!vomng.frame_first || !vomng.frame_last) { mp_msg(MSGT_VO, MSGL_ERR, "vomng: not creating empty file\n"); return 1; } /* create output file */ file = fopen(vomng.out_file_name, "wb"); if (!file) { mp_msg(MSGT_VO, MSGL_ERR, "vomng: could not open output file \"%s\": %s\n", vomng.out_file_name, strerror(errno)); return 1; } /* inititalize MNG library */ mng = mng_initialize(file, vomng_alloc, vomng_free, MNG_NULL); if (!mng) { mp_msg(MSGT_VO, MSGL_ERR, "vomng: could not initialize libmng\n"); fclose(file); return 1; } if (mng_setcb_openstream (mng, vomng_openstream ) || mng_setcb_closestream(mng, vomng_closestream) || mng_setcb_writedata (mng, vomng_writedata )) { mp_msg(MSGT_VO, MSGL_ERR, "vomng: cannot set callbacks for libmng\n"); mng_cleanup(&mng); fclose(file); return 1; } /* create new MNG image in memory */ if (mng_create(mng)) { mp_msg(MSGT_VO, MSGL_ERR, "vomng: cannot create MNG image in memory\n"); mng_cleanup(&mng); fclose(file); return 1; } /* determine number of frames and total duration */ frames = 0; for (frame = vomng.frame_first; frame; frame = frame->next) frames++; duration_ms = vomng.frame_last->time_ms - vomng.frame_first->time_ms; /* write MNG header chunks */ if (mng_putchunk_mhdr(mng, vomng.width, /* dimensions */ vomng.height, 1000, 0, /* ticks per second, layer */ frames, /* number of frames */ duration_ms, /* total duration */ MNG_SIMPLICITY_VALID | MNG_SIMPLICITY_SIMPLEFEATURES | MNG_SIMPLICITY_COMPLEXFEATURES) || mng_putchunk_save(mng, MNG_TRUE, 0, 0) || /* empty save chunk */ mng_putchunk_term(mng, MNG_TERMACTION_CLEAR, /* show last frame forever */ MNG_ITERACTION_CLEAR, 0, 0)) { mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing MHDR/SAVE/TERM chunks failed\n"); mng_write(mng); /* write out buffered chunks before cleanup */ mng_cleanup(&mng); fclose(file); return 1; } /* write frames */ first = 1; for (frame = vomng.frame_first; frame; frame = frame->next) { if (vomng_write_frame(frame, mng, vomng.width, vomng.height, first)) break; first = 0; } if (frame) { mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing frames failed\n"); mng_write(mng); /* write out buffered chunks before cleanup */ mng_cleanup(&mng); fclose(file); return 1; } /* write MNG end chunk */ if (mng_putchunk_mend(mng)) { mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing end chunk failed\n"); mng_write(mng); /* write out buffered chunks before cleanup */ mng_cleanup(&mng); fclose(file); return 1; } /* finish and cleanup */ mng_write(mng); /* write out buffered chunks before cleanup */ mng_cleanup(&mng); fclose(file); return 0; }