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 }
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); }
/** * @brief write frame to MNG file * @param[in] *frame the frame to write to MNG file * @param[in] mng libmng handle * @param[in] width width of canvas * @param[in] height height of canvas * @param[in] first_frame if the frame is the first one in the file * @return 0 on success, 1 on error */ static int vomng_write_frame(struct vomng_frame *frame, mng_handle mng, unsigned int width, unsigned int height, int first_frame) { unsigned int delay_ms; /* determine delay */ if (frame->next) delay_ms = frame->next->time_ms - frame->time_ms; else delay_ms = VOMNG_DEFAULT_DELAY_MS; /* default delay for last frame */ /* write frame headers to MNG file */ if (mng_putchunk_seek(mng, 0, MNG_NULL)) { mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing SEEK chunk failed\n"); return 1; } if (mng_putchunk_fram(mng, MNG_FALSE, /* keep canvas if not 1st frame */ first_frame ? MNG_FRAMINGMODE_1 : MNG_FRAMINGMODE_NOCHANGE, 0, MNG_NULL, /* no frame name */ MNG_CHANGEDELAY_DEFAULT, /* change only delay */ MNG_CHANGETIMOUT_NO, MNG_CHANGECLIPPING_NO, MNG_CHANGESYNCID_NO, delay_ms, /* new delay */ 0, /* no new timeout */ 0, 0, 0, 0, 0, /* no new boundary */ 0, 0)) { /* no count, no IDs */ mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing FRAM chunk failed\n"); return 1; } if (mng_putchunk_defi(mng, 0, /* no ID */ MNG_DONOTSHOW_VISIBLE, MNG_ABSTRACT, MNG_TRUE, 0, 0, /* top left location */ MNG_FALSE, 0, 0, 0, 0)) { /* no clipping */ mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing DEFI chunk failed\n"); return 1; } if (mng_putchunk_ihdr(mng, width, height, /* dimensions */ 8, MNG_COLORTYPE_RGB, /* RBG */ MNG_COMPRESSION_DEFLATE, MNG_FILTER_ADAPTIVE, MNG_INTERLACE_NONE)) { mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing IHDR chunk failed\n"); return 1; } /* write frame data */ if (mng_putchunk_idat(mng, frame->len, frame->data)) { mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing IDAT chunk failed\n"); return 1; } /* write frame footers to MNG file */ if (mng_putchunk_iend(mng)) { mp_msg(MSGT_VO, MSGL_ERR, "vomng: writing IEND chunk failed\n"); return 1; } return 0; }