BlinkenArea - GitList
Repositories
Blog
Wiki
BlinkenLib
Code
Commits
Branches
Tags
Search
Tree:
860e91b
Branches
Tags
master
v0.1
v0.2
v0.3
v0.3.1
v0.4
v0.4.1
v0.5
v0.5.1
v0.5.2
v0.5.3
v0.5.4
v0.5.5
v0.6.0
v0.6.1
v0.6.2
v0.6.3
v0.6.4
v0.6.5
v0.6.6
v0.6.7
v0.6.8
v0.6.9
v0.7.0
v0.7.1
v0.7.10
v0.7.2
v0.7.3
v0.7.4
v0.7.5
v0.7.6
v0.7.7
v0.7.8
v0.7.9
v0.8.0
v0.8.1
BlinkenLib
src
BlinkenMng.c
format
Stefan Schuermans
commited
860e91b
at 2023-08-18 09:55:23
BlinkenMng.c
Blame
History
Raw
/* BlinkenLib Copyright 2004-2014 Stefan Schuermans <stefan@schuermans.info> Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html a blinkenarea.org project */ #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <zlib.h> #define MNG_INCLUDE_WRITE_PROCS #define MNG_ACCESS_CHUNKS #define MNG_SUPPORT_READ #define MNG_SUPPORT_DISPLAY #define MNG_SUPPORT_WRITE #include <libmng.h> #include <BlinkenLib/BlinkenFrame.h> #include <BlinkenLib/BlinkenMovie.h> #include <BlinkenLib/config.h> #include "BlinkenConstants.h" #include "BlinkenMng.h" // type for state of the MNG read/write process typedef struct _BlMng_userdata { FILE *hFile; // handle of open file unsigned int width; // size of canvas unsigned int height; unsigned char *canvas; // canvas (i.e. pixel data, size is height * // width * 3) unsigned int counter_msec; // current number of milliseconds unsigned int timer_msec; // the MNG library will be called after this // number of milliseconds stBlinkenMovie *movie; // movie being created } BlMng_userdata; mng_ptr BlMng_alloc(mng_size_t iSize) { void *ptr = malloc(iSize); if (ptr != NULL) memset(ptr, 0, iSize); return (mng_ptr)ptr; } void BlMng_free(mng_ptr pPtr, mng_size_t __attribute__((unused)) iSize) { free(pPtr); } mng_bool BlMng_openstream(mng_handle __attribute__((unused)) hMNG) { return MNG_TRUE; } mng_bool BlMng_closestream(mng_handle __attribute__((unused)) hMNG) { return MNG_TRUE; } // read data from the open file mng_bool BlMng_readdata(mng_handle hMNG, mng_ptr pBuf, mng_uint32 iSize, mng_uint32 *iRead) { BlMng_userdata *data = (BlMng_userdata *)mng_get_userdata(hMNG); *iRead = fread(pBuf, 1, iSize, data->hFile); return MNG_TRUE; } // write data to the open file mng_bool BlMng_writedata(mng_handle hMNG, mng_ptr pBuf, mng_uint32 iSize, mng_uint32 *iWritten) { BlMng_userdata *data = (BlMng_userdata *)mng_get_userdata(hMNG); *iWritten = fwrite(pBuf, 1, iSize, data->hFile); return MNG_TRUE; } // MNG library has processed the header information mng_bool BlMng_processheader(mng_handle hMNG, mng_uint32 iWidth, mng_uint32 iHeight) { BlMng_userdata *data = (BlMng_userdata *)mng_get_userdata(hMNG); // free old canvas (in case there is already one) if (data->canvas != NULL) { free(data->canvas); data->canvas = NULL; printf("</blm>\n"); } // free old movie if (data->movie) { BlinkenMovieFree(data->movie); data->movie = NULL; } // remember dimensions and allocate new canvas data->width = iWidth; data->height = iHeight; data->canvas = (unsigned char *)malloc(iHeight * iWidth * 3); if (data->canvas == NULL) return MNG_FALSE; data->counter_msec = 0; data->timer_msec = 0; // create new empty movie data->movie = BlinkenMovieNew(iHeight, iWidth, 3, 255); // always 24 // bit RGB if (data->movie == NULL) return MNG_FALSE; return MNG_TRUE; } // get access to a line of the canvas mng_ptr BlMng_getcanvasline(mng_handle hMNG, mng_uint32 iLinenr) { BlMng_userdata *data = (BlMng_userdata *)mng_get_userdata(hMNG); // return pointer to line if (iLinenr < data->height) return (mng_ptr)(data->canvas + iLinenr * data->width * 3); else return (mng_ptr)NULL; } // the MNG library thinks that (a part of) the canvas should be shown mng_bool BlMng_refresh(mng_handle __attribute__((unused)) hMNG, mng_uint32 __attribute__((unused)) iX, mng_uint32 __attribute__((unused)) iY, mng_uint32 __attribute__((unused)) iWidth, mng_uint32 __attribute__((unused)) iHeight) { // nothing to do here for BlinkenLib as we are only converting and not // displaying return MNG_TRUE; } // the MNG library want to know how many milliseconds have passed mng_uint32 BlMng_gettickcount(mng_handle hMNG) { BlMng_userdata *data = (BlMng_userdata *)mng_get_userdata(hMNG); // just return the current number of milliseconds (only converting, do not // use real time for this) return data->counter_msec; } // the MNG library wants to be be called again after some milliseconds mng_bool BlMng_settimer(mng_handle hMNG, mng_uint32 iMsecs) { BlMng_userdata *data = (BlMng_userdata *)mng_get_userdata(hMNG); // just remember the number of milliseconds data->timer_msec = iMsecs; return MNG_TRUE; } // save as new frame to current movie void BlMng_save_frame(stBlinkenMovie *movie, unsigned int msec, unsigned int width, unsigned int height, unsigned char *data) { stBlinkenFrame *frame; unsigned int c, x, y; // check that a movie is present if (movie == NULL) return; // create new frame (24 bit RGB) frame = BlinkenFrameNew(height, width, 3, 255, msec); if (frame == NULL) return; // set pixels of frame for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { for (c = 0; c < 3; c++) { BlinkenFrameSetPixel(frame, y, x, c, *data); data++; } } } // append frame to movie if (BlinkenMovieAppendFrame(movie, frame) != 0) BlinkenFrameFree(frame); } // compress pixel data in a frame void BlMng_compress_frame(stBlinkenFrame *frame, unsigned char **p_out_ptr, unsigned int *p_out_len) { unsigned int width = BlinkenFrameGetWidth(frame); unsigned int height = BlinkenFrameGetHeight(frame); // channels is always 3, maxval is always 255 - this function is only // called with frames from moivies with those parameters unsigned int row_stride, raw_len, y, x, c; unsigned char *raw_ptr, *ptr, *out_ptr; unsigned long out_len; // assemble raw data to compress row_stride = width * 3 + 1; // rows are preceeded by filter ID raw_len = row_stride * height; raw_ptr = (unsigned char *)malloc(raw_len); ptr = raw_ptr; for (y = 0; y < height; y++) { // filter ID *ptr = MNG_FILTER_NONE; // no filtering ptr++; // pixel data of row for (x = 0; x < width; x++) { for (c = 0; c < 3; c++) { *ptr = BlinkenFrameGetPixel(frame, y, x, c); ptr++; } } } // for( y ... // compress data out_len = raw_len + (raw_len + 999) / 1000 + 12; // must be at least // <source length> * // 1.001 + 12 out_ptr = (unsigned char *)malloc(out_len); compress2(out_ptr, &out_len, raw_ptr, raw_len, Z_BEST_COMPRESSION); free(raw_ptr); // return allocated compressed data *p_out_ptr = out_ptr; *p_out_len = (unsigned int)out_len; } // load a MNG file as BlinkenMovie stBlinkenMovie *BlinkenMngLoad(const char *pFilename) { BlMng_userdata *data; mng_handle hMNG; stBlinkenMovie *movie = NULL; if (pFilename == NULL) return NULL; // create new structure for state of operation data = (BlMng_userdata *)malloc(sizeof(BlMng_userdata)); if (data != NULL) { data->hFile = NULL; data->width = 0; data->height = 0; data->canvas = NULL; data->movie = NULL; // open MNG file for reading data->hFile = fopen(pFilename, "rb"); if (data->hFile != NULL) { // initialize MNG library hMNG = mng_initialize((mng_ptr)data, BlMng_alloc, BlMng_free, MNG_NULL); if (hMNG) { // set MNG callbacks if (mng_setcb_openstream(hMNG, BlMng_openstream) == 0 && mng_setcb_closestream(hMNG, BlMng_closestream) == 0 && mng_setcb_readdata(hMNG, BlMng_readdata) == 0 && mng_setcb_processheader(hMNG, BlMng_processheader) == 0 && mng_setcb_getcanvasline(hMNG, BlMng_getcanvasline) == 0 && mng_setcb_refresh(hMNG, BlMng_refresh) == 0 && mng_setcb_gettickcount(hMNG, BlMng_gettickcount) == 0 && mng_setcb_settimer(hMNG, BlMng_settimer) == 0 && mng_set_canvasstyle(hMNG, MNG_CANVAS_RGB8) == 0) { // request not to do lopping // (stop with error at end of first iteration intead) if (mng_set_cacheplayback(hMNG, MNG_FALSE) == 0) { // read MNG file to memory and start "displaying" mng_retcode iRC = mng_readdisplay(hMNG); if (iRC == 0 || iRC == MNG_NEEDTIMERWAIT) { // "display" loop int b_saved = 0; unsigned int frame_idx = 0; while (iRC == MNG_NEEDTIMERWAIT && data->timer_msec > 0) { // save fame to movie BlMng_save_frame(data->movie, data->timer_msec, data->width, data->height, data->canvas); b_saved = 1; ++frame_idx; // let required time elapse for MNG library data->counter_msec += data->timer_msec; data->timer_msec = 0; // "display" next frame iRC = mng_display_resume(hMNG); if (iRC != 0 && iRC != MNG_NEEDTIMERWAIT) break; } // while( iRC == MNG_NEEDTIMERWAIT && data->timer_msec > 0 ) // save single frame if no frame saved yet (i.e. not an // animated MNG image) if (!b_saved) BlMng_save_frame(data->movie, data->timer_msec, data->width, data->height, data->canvas); // if movie has more than one frame and the last frame is empty // // // // // // with duration of 1ms, remove last frame // - a black frame with 1ms duration is inserted by MNG library // // // // // // when MNG requests to clean canvas after displaying // - the reqauest to clear canvas after end of MNG anmation is // needed to preserve duration of last frame (when converting // from native format to MNG) // - remove this frame that is created by the described side // effect { unsigned int frame_cnt = BlinkenMovieGetFrameCnt(data->movie); if (frame_cnt >= 1) { stBlinkenFrame *frame = BlinkenMovieGetFrame(data->movie, frame_cnt - 1); if (frame != NULL && BlinkenFrameIsEmpty(frame)) BlinkenMovieDeleteFrame(data->movie, frame_cnt - 1); } } // stop "displaying" mng_display_freeze(hMNG); } // if( iRC == 0 || iRC == MNG_NEEDTIMERWAIT ) } // if (mng_set_cacheplayback ... ) } // if( mng_setcb_... ) // shutdown MNG library mng_cleanup(&hMNG); } // if( hMNG ) // close file fclose(data->hFile); } // if( data->hFile != NULL ) // fetch created movie from state movie = data->movie; data->movie = NULL; // free data structure for state if (data->canvas != NULL) free(data->canvas); free(data); } // if( data != NULL ) return movie; } int BlinkenMngSave(stBlinkenMovie *pMovie, const char *pFilename) { BlMng_userdata *data; mng_handle hMNG; int b_success = 0; if (pMovie == NULL || pFilename == NULL) return -1; // create new structure for state of operation data = (BlMng_userdata *)malloc(sizeof(BlMng_userdata)); if (data != NULL) { data->hFile = NULL; data->width = BlinkenMovieGetWidth(pMovie); data->height = BlinkenMovieGetHeight(pMovie); data->canvas = NULL; data->movie = NULL; // convert movie to suitable format data->movie = BlinkenMovieClone(pMovie); if (data->movie != NULL) { BlinkenMovieResize(data->movie, data->height, data->width, 3, 255); // open MNG file for writing data->hFile = fopen(pFilename, "wb"); if (data->hFile != NULL) { // initialize MNG library hMNG = mng_initialize((mng_ptr)data, BlMng_alloc, BlMng_free, MNG_NULL); if (hMNG) { // set MNG callbacks if (mng_setcb_openstream(hMNG, BlMng_openstream) == 0 && mng_setcb_closestream(hMNG, BlMng_closestream) == 0 && mng_setcb_writedata(hMNG, BlMng_writedata) == 0) { // create new MNG image in memory if (mng_create(hMNG) == 0) { // write MNG chunks do { unsigned int frame_cnt = BlinkenMovieGetFrameCnt(data->movie); unsigned int total_duration = BlinkenMovieGetDuration(data->movie); unsigned int frame_no; // write MG header chunks if (mng_putchunk_mhdr( hMNG, data->width, data->height, // dimensions 1000, 0, frame_cnt, total_duration, // ticks // // // // // // per // second, // layer, // frame // count, // duration MNG_SIMPLICITY_VALID | MNG_SIMPLICITY_SIMPLEFEATURES | MNG_SIMPLICITY_COMPLEXFEATURES // simplicity ) != 0) break; if (mng_putchunk_save(hMNG, MNG_TRUE, // empty SAVE chunk 0, 0 // no offset, no count ) != 0) break; // tell MNG player to clear canvas after end of MNG animation // - this is needed to preserve duration of last frame // - this results in a black frame with 1ms duration at the // end of the MNG as side effect if (mng_putchunk_term(hMNG, MNG_TERMACTION_CLEAR, MNG_ITERACTION_CLEAR, // show // // // // // // last // frame // forever // after // end // of // animation 0, 0 // no repeat delay, no // maximum iteration count ) != 0) break; // write frames for (frame_no = 0; frame_no < frame_cnt; frame_no++) { // get current frame stBlinkenFrame *frame = BlinkenMovieGetFrame(data->movie, frame_no); unsigned int frame_duration = BlinkenFrameGetDuration(frame); // write MNG frame header chunks if (mng_putchunk_seek(hMNG, 0, MNG_NULL // no name ) != 0) break; if (mng_putchunk_fram( hMNG, MNG_FALSE, frame_no == 0 ? MNG_FRAMINGMODE_1 : MNG_FRAMINGMODE_NOCHANGE, // empty, // // // // // // mode 0, MNG_NULL, // frame name MNG_CHANGEDELAY_DEFAULT, MNG_CHANGETIMOUT_NO, MNG_CHANGECLIPPING_NO, MNG_CHANGESYNCID_NO, // changing // // // // // // only // delay frame_duration, 0, // new delay, // // // // // // no new // timeout 0, 0, 0, 0, 0, // no new boundary 0, 0 // no count, no IDs ) != 0) break; if (mng_putchunk_defi(hMNG, 0, MNG_DONOTSHOW_VISIBLE, MNG_ABSTRACT, // no // // // // // // ID, // visible, // abstract MNG_TRUE, 0, 0, // top left location MNG_FALSE, 0, 0, 0, 0 // no // clipping ) != 0) break; if (mng_putchunk_ihdr(hMNG, data->width, data->height, 8, MNG_COLORTYPE_RGB, // dimensions // // // // // // and // 8 // bit // RGB MNG_COMPRESSION_DEFLATE, MNG_FILTER_ADAPTIVE, MNG_INTERLACE_NONE // deflate // // // // // // compression, // adaptive // filter, // no // interlacing ) != 0) break; // write MNG frame data { unsigned char *ptr; unsigned int len; mng_retcode iRC; BlMng_compress_frame(frame, &ptr, &len); iRC = mng_putchunk_idat(hMNG, (mng_uint32)len, (mng_ptr)ptr); free(ptr); if (iRC != 0) break; } // write MNG frame footer if (mng_putchunk_iend(hMNG) != 0) break; } // for( frame ... if (frame_no < frame_cnt) // if not all frames have // been processed there was // en error break; // output MNG end chunk if (mng_putchunk_mend(hMNG) != 0) break; // write MNG chuks to file if (mng_write(hMNG) != 0) break; b_success = 1; } while (0); } // if( mng_create( hMNG ) == 0 ) } // if( mng_setcb_... ) // shutdown MNG library mng_cleanup(&hMNG); } // if( hMNG ) // close file fclose(data->hFile); } // if( data->hFile != NULL ) // free movie BlinkenMovieFree(data->movie); } // if( data->movie != NULL ) // free data structure for state if (data->canvas != NULL) free(data->canvas); free(data); } // if( data != NULL ) return b_success ? 0 : -1; }