void image_jpeg_destroy(MediaScanImage *i) { if (i->_jpeg) { JPEGData *j = (JPEGData *)i->_jpeg; jpeg_destroy_decompress(j->cinfo); LOG_MEM("destroy JPEG cinfo @ %p\n", j->cinfo); free(j->cinfo); LOG_MEM("destroy JPEG error_pub @ %p\n", j->jpeg_error_pub); free(j->jpeg_error_pub); LOG_MEM("destroy JPEGData @ %p\n", i->_jpeg); free(i->_jpeg); i->_jpeg = NULL; } }
static void buf_dst_mgr_term(j_compress_ptr cinfo) { struct buf_dst_mgr *dst = (void *)cinfo->dest; size_t sz = BUF_SIZE - dst->jdst.free_in_buffer; if (sz > 0) { // Copy final buffer to image data buffer_append(dst->dbuf, dst->buf, sz); } LOG_MEM("destroy JPEG buf @ %p\n", dst->buf); free(dst->buf); LOG_MEM("buf_dst_mgr_term, copied final %zd bytes (total bytes %d)\n", sz, buffer_len(dst->dbuf)); }
MediaScanResult * result_create(void) { MediaScanResult *r = (MediaScanResult *)calloc(sizeof(MediaScanResult), 1); if (r == NULL) { LOG_ERROR("Out of memory for new MediaScanResult object\n"); return NULL; } LOG_MEM("new MediaScanResult @ %p\n", r); r->type = TYPE_UNKNOWN; r->flags = USE_EXTENSION; r->path = NULL; r->error = NULL; r->mime_type = NULL; r->dlna_profile = NULL; r->size = 0; r->mtime = 0; r->bitrate = 0; r->duration_ms = 0; r->type_data.audio = NULL; r->_avf = NULL; r->_fp = NULL; return r; }
// Destination manager to copy compressed data to a buffer static void buf_dst_mgr_init(j_compress_ptr cinfo) { struct buf_dst_mgr *dst = (void *)cinfo->dest; // Temporary internal buffer dst->buf = (JOCTET *)malloc(BUF_SIZE); LOG_MEM("new JPEG buf @ %p\n", dst->buf); // Storage for full compressed data dst->dbuf = (Buffer *)malloc(sizeof(Buffer)); LOG_MEM("new JPEG dbuf @ %p\n", dst->dbuf); buffer_init(dst->dbuf, BUF_SIZE); dst->off = dst->buf; dst->jdst.next_output_byte = dst->off; dst->jdst.free_in_buffer = BUF_SIZE; }
MediaScan *ms_create(void) { MediaScan *s = NULL; dlna_t *dlna = NULL; _init(); s = (MediaScan *)calloc(sizeof(MediaScan), 1); if (s == NULL) { ms_errno = MSENO_MEMERROR; FATAL("Out of memory for new MediaScan object\n"); return NULL; } LOG_MEM("new MediaScan @ %p\n", s); s->flags = MS_USE_EXTENSION | MS_FULL_SCAN; s->watch_interval = 600; // 10 minutes s->thread = NULL; s->dbp = NULL; s->progress = progress_create(); // List of all dirs found s->_dirq = malloc(sizeof(struct dirq)); SIMPLEQ_INIT((struct dirq *)s->_dirq); // We can't use libdlna's init function because it loads everything in ffmpeg dlna = (dlna_t *)calloc(sizeof(dlna_t), 1); dlna->inited = 1; s->_dlna = (void *)dlna; dlna_register_all_media_profiles(dlna); return s; } /* ms_create() */
inline void Gbs_Core::write_io( int offset, int data ) { (void) LOG_MEM( offset + io_base, "<", data ); ram [io_base - ram_addr + offset] = data; if ( (unsigned) offset < 0x80 ) write_io_( offset, data ); }
void image_png_destroy(MediaScanImage *i) { if (i->_png) { PNGData *p = (PNGData *)i->_png; png_destroy_read_struct(&p->png_ptr, &p->info_ptr, NULL); LOG_MEM("destroy PNGData @ %p\n", i->_png); free(i->_png); i->_png = NULL; } }
MediaScanVideo *video_create(void) { MediaScanVideo *v = (MediaScanVideo *)calloc(sizeof(MediaScanVideo), 1); if (v == NULL) { ms_errno = MSENO_MEMERROR; FATAL("Out of memory for new MediaScanVideo object\n"); return NULL; } LOG_MEM("new MediaScanVideo @ %p\n", v); return v; }
void tag_destroy(MediaScanTag *t) { int i; // free items if (t->nitems) { for (i = 0; i < t->nitems; i++) tag_item_destroy(t->items[i]); } LOG_MEM("destroy MediaScanTag @ %p\n", t); free(t); }
MediaScanTag *tag_create(const char *type) { MediaScanTag *t = (MediaScanTag *)calloc(sizeof(MediaScanTag), 1); if (t == NULL) { ms_errno = MSENO_MEMERROR; FATAL("Out of memory for new MediaScanTag object\n"); return NULL; } t->type = type; t->nitems = 0; LOG_MEM("new MediaScanTag @ %p\n", t); return t; }
static boolean buf_dst_mgr_empty(j_compress_ptr cinfo) { struct buf_dst_mgr *dst = (void *)cinfo->dest; // Copy tmp buffer to image buffer buffer_append(dst->dbuf, dst->buf, BUF_SIZE); // Reuse the tmp buffer for the next chunk dst->off = dst->buf; dst->jdst.next_output_byte = dst->off; dst->jdst.free_in_buffer = BUF_SIZE; LOG_MEM("buf_dst_mgr_empty, copied %d bytes (total now %d)\n", BUF_SIZE, buffer_len(dst->dbuf)); return TRUE; }
int Gbs_Core::read_mem( addr_t addr ) { int result = *cpu.get_code( addr ); if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size ) result = apu_.read_register( time(), addr ); #ifndef NDEBUG else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 ) dprintf( "Unmapped read $%04X\n", (unsigned) addr ); else if ( unsigned (addr - 0xFF01) < 0xFF80 - 0xFF01 && addr != 0xFF70 && addr != 0xFF05 ) dprintf( "Unmapped read $%04X\n", (unsigned) addr ); #endif return LOG_MEM( addr, ">", result ); }
int image_png_read_header(MediaScanImage *i, MediaScanResult *r) { int x; PNGData *p = malloc(sizeof(PNGData)); i->_png = (void *)p; LOG_MEM("new PNGData @ %p\n", i->_png); p->buf = (Buffer *)r->_buf; p->fp = r->_fp; p->path = r->path; p->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp) p, image_png_error, image_png_warning); if (!p->png_ptr) FATAL("Could not initialize libpng\n"); p->info_ptr = png_create_info_struct(p->png_ptr); if (!p->info_ptr) { png_destroy_read_struct(&p->png_ptr, (png_infopp) NULL, (png_infopp) NULL); FATAL("Could not initialize libpng\n"); } if (setjmp(png_jmpbuf(p->png_ptr))) { image_png_destroy(i); return 0; } png_set_read_fn(p->png_ptr, p, image_png_read_buf); png_read_info(p->png_ptr, p->info_ptr); i->width = png_get_image_width(p->png_ptr, p->info_ptr); i->height = png_get_image_height(p->png_ptr, p->info_ptr); i->channels = png_get_channels(p->png_ptr, p->info_ptr); i->has_alpha = 1; r->mime_type = MIME_IMAGE_PNG; // Match with DLNA profile // DLNA does not support interlaced images if (png_get_interlace_type(p->png_ptr, p->info_ptr) == PNG_INTERLACE_NONE) { for (x = 0; png_profiles_mapping[x].profile; x++) { if (i->width <= png_profiles_mapping[x].max_width && i->height <= png_profiles_mapping[x].max_height) { r->dlna_profile = png_profiles_mapping[x].profile->id; break; } } } return 1; }
int Nsf_Impl::read_mem( addr_t addr ) { int result = low_ram [addr & (low_ram_size-1)]; // also handles wrap-around if ( addr & 0xE000 ) { result = *cpu.get_code( addr ); if ( addr < sram_addr ) { if ( addr == apu.status_addr ) result = apu.read_status( time() ); else result = cpu_read( addr ); } } return LOG_MEM( addr, ">", result ); }
int Gbs_Core::read_io( int offset ) { int const io_base = 0xFF00; int result = ram [io_base - ram_addr + offset]; if ( (unsigned) (offset - (apu_.io_addr - io_base)) < apu_.io_size ) { result = apu_.read_register( time(), offset + io_base ); (void) LOG_MEM( offset + io_base, ">", result ); } else { check( result == read_mem( offset + io_base ) ); } return result; }
void ms_destroy(MediaScan *s) { int i; if (s->thread) { // A thread is running, tell it to abort ms_abort(s); // thread_destroy will wait until the thread exits cleanly thread_destroy(s->thread); s->thread = NULL; } for (i = 0; i < s->npaths; i++) { free(s->paths[i]); } for (i = 0; i < s->nignore_exts; i++) { free(s->ignore_exts[i]); } for (i = 0; i < s->nignore_sdirs; i++) { free(s->ignore_sdirs[i]); } for (i = 0; i < s->nthumbspecs; i++) { free(s->thumbspecs[i]); } progress_destroy(s->progress); free(s->_dirq); free(s->_dlna); if (s->cachedir) free(s->cachedir); /* When we're done with the database, close it. */ bdb_destroy(s); LOG_MEM("destroy MediaScan @ %p\n", s); free(s); } /* ms_destroy() */
void Nsf_Impl::write_mem( addr_t addr, int data ) { (void) LOG_MEM( addr, "<", data ); int offset = addr - sram_addr; if ( (unsigned) offset < sram_size ) { sram() [offset] = data; } else { // after sram because CPU handles most low_ram accesses internally already int temp = addr & (low_ram_size-1); // also handles wrap-around if ( !(addr & 0xE000) ) { low_ram [temp] = data; } else { int bank = addr - banks_addr; if ( (unsigned) bank < bank_count ) { write_bank( bank, data ); } else if ( (unsigned) (addr - apu.io_addr) < apu.io_size ) { apu.write_register( time(), addr, data ); } else { #if !NSF_EMU_APU_ONLY // 0x8000-0xDFFF is writable int i = addr - 0x8000; if ( (unsigned) i < fdsram_size && fds_enabled() ) fdsram() [i] = data; else #endif cpu_write( addr, data ); } } } }
void result_destroy(MediaScanResult *r) { if (r->error) error_destroy(r->error); switch (r->type) { case TYPE_VIDEO: if (r->type_data.video) video_destroy((MediaScanVideo *)r->type_data.video); break; // XXX other type_data types default: break; } LOG_MEM("destroy MediaScanResult @ %p\n", r); free(r); }
///------------------------------------------------------------------------------------------------- /// Begin a recursive scan of all paths previously provided to ms_add_path(). If async mode /// is enabled, this call will return immediately. You must obtain the file descriptor using /// ms_async_fd and this must be checked using an event loop or select(). When the fd becomes /// readable you must call ms_async_process to trigger any necessary callbacks. /// /// @author Andy Grundman /// @date 03/15/2011 /// /// @param [in,out] s If non-null, the. /// /// ### remarks . ///------------------------------------------------------------------------------------------------- void ms_scan(MediaScan *s) { if (s->on_result == NULL) { LOG_ERROR("Result callback not set, aborting scan\n"); goto out; } if (s->npaths == 0) { LOG_ERROR("No paths set, aborting scan\n"); goto out; } if (s->async) { thread_data_type *thread_data; thread_data = (thread_data_type *)calloc(sizeof(thread_data_type), 1); LOG_MEM("new thread_data @ %p\n", thread_data); thread_data->lpDir = NULL; thread_data->s = s; s->thread = thread_create(do_scan, thread_data, s->async_fds); if (!s->thread) { LOG_ERROR("Unable to start async thread\n"); goto out; } } else { thread_data_type thread_data; thread_data.s = s; thread_data.lpDir = NULL; do_scan(&thread_data); } out: return; } /* ms_scan() */
void Gbs_Core::write_mem( addr_t addr, int data ) { (void) LOG_MEM( addr, "<", data ); int offset = addr - ram_addr; if ( (unsigned) offset < 0x10000 - ram_addr ) { ram [offset] = data; offset -= 0xE000 - ram_addr; if ( (unsigned) offset < 0x1F80 ) write_io_inline( offset, data, 0xE000 ); } else if ( (unsigned) (offset - (0x2000 - ram_addr)) < 0x2000 ) { set_bank( data & 0xFF ); } #ifndef NDEBUG else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 ) { dprintf( "Unmapped write $%04X\n", (unsigned) addr ); } #endif }
// Called by ms_scan either in a thread or synchronously static void *do_scan(void *userdata) { MediaScan *s = ((thread_data_type *)userdata)->s; int i; struct dirq *dir_head = (struct dirq *)s->_dirq; struct dirq_entry *dir_entry = NULL; struct fileq *file_head = NULL; struct fileq_entry *file_entry = NULL; char tmp_full_path[MAX_PATH_STR_LEN]; // Initialize the cache database if (!init_bdb(s)) { MediaScanError *e = error_create("", MS_ERROR_CACHE, "Unable to initialize libmediascan cache"); send_error(s, e); goto out; } if (s->flags & MS_CLEARDB) { reset_bdb(s); } if (s->progress == NULL) { MediaScanError *e = error_create("", MS_ERROR_TYPE_INVALID_PARAMS, "Progress object not created"); send_error(s, e); goto out; } // Build a list of all directories and paths // We do this first so we can present an accurate scan eta later progress_start_phase(s->progress, "Discovering"); for (i = 0; i < s->npaths; i++) { LOG_INFO("Scanning %s\n", s->paths[i]); recurse_dir(s, s->paths[i], 0); } // Scan all files found progress_start_phase(s->progress, "Scanning"); while (!SIMPLEQ_EMPTY(dir_head)) { dir_entry = SIMPLEQ_FIRST(dir_head); file_head = dir_entry->files; while (!SIMPLEQ_EMPTY(file_head)) { // check if the scan has been aborted if (s->_want_abort) { LOG_DEBUG("Aborting scan\n"); goto aborted; } file_entry = SIMPLEQ_FIRST(file_head); // Construct full path strcpy(tmp_full_path, dir_entry->dir); #ifdef WIN32 strcat(tmp_full_path, "\\"); #else strcat(tmp_full_path, "/"); #endif strcat(tmp_full_path, file_entry->file); ms_scan_file(s, tmp_full_path, file_entry->type); // Send progress update if necessary if (s->on_progress) { s->progress->done++; if (progress_update(s->progress, tmp_full_path)) send_progress(s); } SIMPLEQ_REMOVE_HEAD(file_head, entries); free(file_entry->file); free(file_entry); } SIMPLEQ_REMOVE_HEAD(dir_head, entries); free(dir_entry->dir); free(dir_entry->files); free(dir_entry); } // Send final progress callback if (s->on_progress) { progress_update(s->progress, NULL); send_progress(s); } LOG_DEBUG("Finished scanning\n"); out: if (s->on_finish) send_finish(s); aborted: if (s->async) { LOG_MEM("destroy thread_data @ %p\n", userdata); free(userdata); } return NULL; }
void video_destroy(MediaScanVideo *v) { LOG_MEM("destroy MediaScanVideo @ %p\n", v); free(v); }
MediaScanImage *video_create_image_from_frame(MediaScanVideo *v, MediaScanResult *r) { MediaScanImage *i = image_create(); AVFormatContext *avf = (AVFormatContext *)r->_avf; av_codecs_t *codecs = (av_codecs_t *)v->_codecs; AVCodec *codec = (AVCodec *)v->_avc; AVFrame *frame = NULL; AVPacket packet; struct SwsContext *swsc = NULL; int got_picture; int64_t duration_tb = ((double)avf->duration / AV_TIME_BASE) / av_q2d(codecs->vs->time_base); uint8_t *src; int x, y; int ofs = 0; int no_keyframe_found = 0; int skipped_frames = 0; if ((avcodec_open(codecs->vc, codec)) < 0) { LOG_ERROR("Couldn't open video codec %s for thumbnail creation\n", codec->name); goto err; } frame = avcodec_alloc_frame(); if (!frame) { LOG_ERROR("Couldn't allocate a video frame\n"); goto err; } av_init_packet(&packet); i->path = v->path; i->width = v->width; i->height = v->height; // XXX select best video frame, for example: // * Skip frames of all the same color (e.g. blank intro frames // * Use edge detection to skip blurry frames // * http://code.google.com/p/fast-edge/ // * http://en.wikipedia.org/wiki/Canny_edge_detector // * Use a frame some percentage into the video, what percentage? // * If really ambitious, use OpenCV for finding a frame with a face? // XXX other ways to seek if this fails // XXX for now, seek 10% into the video av_seek_frame(avf, codecs->vsid, (int)((double)duration_tb * 0.1), 0); for (;;) { int ret; int rgb_bufsize; AVFrame *frame_rgb = NULL; uint8_t *rgb_buffer = NULL; // Give up if we already tried the first frame if (no_keyframe_found) { LOG_ERROR("Error decoding video frame for thumbnail: %s\n", v->path); goto err; } if ((ret = av_read_frame(avf, &packet)) < 0) { if (ret == AVERROR_EOF || skipped_frames > 200) { LOG_DEBUG("Couldn't find a keyframe, using first frame\n"); no_keyframe_found = 1; av_seek_frame(avf, codecs->vsid, 0, 0); av_read_frame(avf, &packet); } else { LOG_ERROR("Couldn't read video frame (%s): ", v->path); print_averror(ret); goto err; } } // Skip frame if it's not from the video stream if (!no_keyframe_found && packet.stream_index != codecs->vsid) { av_free_packet(&packet); skipped_frames++; continue; } // Skip non-key-frames if (!no_keyframe_found && !(packet.flags & AV_PKT_FLAG_KEY)) { av_free_packet(&packet); skipped_frames++; continue; } // Skip invalid packets, not sure why this isn't an error from av_read_frame if (packet.pos < 0) { av_free_packet(&packet); skipped_frames++; continue; } LOG_DEBUG("Using video packet: pos %lld size %d, stream_index %d, duration %d\n", packet.pos, packet.size, packet.stream_index, packet.duration); if ((ret = avcodec_decode_video2(codecs->vc, frame, &got_picture, &packet)) < 0) { LOG_ERROR("Error decoding video frame for thumbnail: %s\n", v->path); print_averror(ret); goto err; } if (!got_picture) { if (skipped_frames > 200) { LOG_ERROR("Error decoding video frame for thumbnail: %s\n", v->path); goto err; } if (!no_keyframe_found) { // Try next frame av_free_packet(&packet); skipped_frames++; continue; } } // use swscale to convert from source format to RGBA in our buffer with no resizing // XXX what scaler is fastest here when not actually resizing? swsc = sws_getContext(i->width, i->height, codecs->vc->pix_fmt, i->width, i->height, PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL); if (!swsc) { LOG_ERROR("Unable to get swscale context\n"); goto err; } frame_rgb = avcodec_alloc_frame(); if (!frame_rgb) { LOG_ERROR("Couldn't allocate a video frame\n"); goto err; } // XXX There is probably a way to get sws_scale to write directly to i->_pixbuf in our RGBA format rgb_bufsize = avpicture_get_size(PIX_FMT_RGB24, i->width, i->height); rgb_buffer = av_malloc(rgb_bufsize); if (!rgb_buffer) { LOG_ERROR("Couldn't allocate an RGB video buffer\n"); av_free(frame_rgb); goto err; } LOG_MEM("new rgb_buffer of size %d @ %p\n", rgb_bufsize, rgb_buffer); avpicture_fill((AVPicture *)frame_rgb, rgb_buffer, PIX_FMT_RGB24, i->width, i->height); // Convert image to RGB24 sws_scale(swsc, frame->data, frame->linesize, 0, i->height, frame_rgb->data, frame_rgb->linesize); // Allocate space for our version of the image image_alloc_pixbuf(i, i->width, i->height); src = frame_rgb->data[0]; ofs = 0; for (y = 0; y < i->height; y++) { for (x = 0; x < i->width * 3; x += 3) { i->_pixbuf[ofs++] = COL(src[x], src[x + 1], src[x + 2]); } src += i->width * 3; } // Free the frame LOG_MEM("destroy rgb_buffer @ %p\n", rgb_buffer); av_free(rgb_buffer); av_free(frame_rgb); // Done! goto out; } err: image_destroy(i); i = NULL; out: sws_freeContext(swsc); av_free_packet(&packet); if (frame) av_free(frame); avcodec_close(codecs->vc); return i; }
int image_jpeg_read_header(MediaScanImage *i, MediaScanResult *r) { int ret = 1; int x; JPEGData *j = malloc(sizeof(JPEGData)); i->_jpeg = (void *)j; LOG_MEM("new JPEGData @ %p\n", i->_jpeg); j->cinfo = malloc(sizeof(struct jpeg_decompress_struct)); j->jpeg_error_pub = malloc(sizeof(struct jpeg_error_mgr)); LOG_MEM("new JPEG cinfo @ %p\n", j->cinfo); LOG_MEM("new JPEG error_pub @ %p\n", j->jpeg_error_pub); j->cinfo->err = jpeg_std_error(j->jpeg_error_pub); j->jpeg_error_pub->error_exit = libjpeg_error_handler; j->jpeg_error_pub->output_message = libjpeg_output_message; if (setjmp(setjmp_buffer)) { image_jpeg_destroy(i); return 0; } // Save filename in case any warnings/errors occur strncpy(Filename, r->path, FILENAME_LEN); if (strlen(r->path) > FILENAME_LEN) Filename[FILENAME_LEN] = 0; jpeg_create_decompress(j->cinfo); // Init custom source manager to read from existing buffer image_jpeg_buf_src(i, r); // Save APP1 marker for EXIF jpeg_save_markers(j->cinfo, 0xE1, 1024 * 64); jpeg_read_header(j->cinfo, TRUE); i->width = j->cinfo->image_width; i->height = j->cinfo->image_height; i->channels = j->cinfo->num_components; r->mime_type = MIME_IMAGE_JPEG; // Match with DLNA profile for (x = 0; jpeg_profiles_mapping[x].profile; x++) { if (i->width <= jpeg_profiles_mapping[x].max_width && i->height <= jpeg_profiles_mapping[x].max_height) { r->dlna_profile = jpeg_profiles_mapping[x].profile->id; break; } } // Process Exif tag if (j->cinfo->marker_list != NULL) { jpeg_saved_marker_ptr marker = j->cinfo->marker_list; while (marker != NULL) { if (marker->marker == 0xE1 && marker->data[0] == 'E' && marker->data[1] == 'x' && marker->data[2] == 'i' && marker->data[3] == 'f') { ExifData *exif; result_create_tag(r, "Exif"); LOG_DEBUG("Parsing EXIF tag of size %d\n", marker->data_length); exif = exif_data_new_from_data(marker->data, marker->data_length); LOG_MEM("new EXIF data @ %p\n", exif); if (exif != NULL) { exif_data_foreach_content(exif, parse_exif_ifd, (void *)r); LOG_MEM("destroy EXIF data @ %p\n", exif); exif_data_free(exif); } break; } marker = marker->next; } } return ret; }
int image_jpeg_load(MediaScanImage *i, MediaScanThumbSpec *spec_hint) { float scale_factor; int x, w, h, ofs; unsigned char *line[1], *ptr = NULL; JPEGData *j = (JPEGData *)i->_jpeg; if (setjmp(setjmp_buffer)) { // See if we have partially decoded an image and hit a fatal error, but still have a usable image if (ptr != NULL) { LOG_MEM("destroy JPEG load ptr @ %p\n", ptr); free(ptr); ptr = NULL; } if (j->cinfo->output_scanline > 0) { LOG_DEBUG("Fatal error but already processed %d scanlines, continuing...\n", j->cinfo->output_scanline); return 1; } image_jpeg_destroy(i); return 0; } // Abort on progressive JPEGs if memory_limit is in use, // as progressive JPEGs can use many MBs of memory and there // is no other easy way to alter libjpeg's memory use /* XXX if (i->memory_limit && j->cinfo->progressive_mode) { LOG_WARN("libmediascan will not decode progressive JPEGs when memory_limit is in use (%s)\n", i->path); image_jpeg_destroy(i); return 0; } */ // XXX If reusing the object a second time, we need to read the header again j->cinfo->do_fancy_upsampling = FALSE; j->cinfo->do_block_smoothing = FALSE; // Choose optimal scaling factor jpeg_calc_output_dimensions(j->cinfo); scale_factor = (float)j->cinfo->output_width / spec_hint->width; if (scale_factor > ((float)j->cinfo->output_height / spec_hint->height)) scale_factor = (float)j->cinfo->output_height / spec_hint->height; if (scale_factor > 1) { // Avoid divide by 0 j->cinfo->scale_denom *= (unsigned int)scale_factor; jpeg_calc_output_dimensions(j->cinfo); } w = j->cinfo->output_width; h = j->cinfo->output_height; // Change the original values to the scaled size i->width = w; i->height = h; LOG_DEBUG("Using JPEG scale factor %d/%d, new source dimensions %d x %d\n", j->cinfo->scale_num, j->cinfo->scale_denom, w, h); // Save filename in case any warnings/errors occur strncpy(Filename, i->path, FILENAME_LEN); if (strlen(i->path) > FILENAME_LEN) Filename[FILENAME_LEN] = 0; // Note: I tested libjpeg-turbo's JCS_EXT_XBGR but it writes zeros // instead of FF for alpha, doesn't support CMYK, etc jpeg_start_decompress(j->cinfo); // Allocate storage for decompressed image image_alloc_pixbuf(i, w, h); ofs = 0; ptr = (unsigned char *)malloc(w * j->cinfo->output_components); line[0] = ptr; LOG_MEM("new JPEG load ptr @ %p\n", ptr); if (j->cinfo->output_components == 3) { // RGB while (j->cinfo->output_scanline < j->cinfo->output_height) { jpeg_read_scanlines(j->cinfo, line, 1); for (x = 0; x < w; x++) { i->_pixbuf[ofs++] = COL(ptr[x + x + x], ptr[x + x + x + 1], ptr[x + x + x + 2]); } } } else if (j->cinfo->output_components == 4) { // CMYK inverted (Photoshop) while (j->cinfo->output_scanline < j->cinfo->output_height) { JSAMPROW row = *line; jpeg_read_scanlines(j->cinfo, line, 1); for (x = 0; x < w; x++) { int c = *row++; int m = *row++; int y = *row++; int k = *row++; i->_pixbuf[ofs++] = COL((c * k) / MAXJSAMPLE, (m * k) / MAXJSAMPLE, (y * k) / MAXJSAMPLE); } } } else { // grayscale while (j->cinfo->output_scanline < j->cinfo->output_height) { jpeg_read_scanlines(j->cinfo, line, 1); for (x = 0; x < w; x++) { i->_pixbuf[ofs++] = COL(ptr[x], ptr[x], ptr[x]); } } } LOG_MEM("destroy JPEG load ptr @ %p\n", ptr); free(ptr); jpeg_finish_decompress(j->cinfo); return 1; }
// Compress the data from i->_pixbuf to i->data. // Uses libjpeg-turbo if available (JCS_EXTENSIONS) for better performance int image_jpeg_compress(MediaScanImage *i, MediaScanThumbSpec *spec) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; struct buf_dst_mgr dst; int quality = spec->jpeg_quality; int x; #ifdef JCS_EXTENSIONS JSAMPROW *data = NULL; #else volatile unsigned char *data = NULL; // volatile = won't be rolled back if longjmp is called JSAMPROW row_pointer[1]; int y, row_stride; #endif if (!i->_pixbuf_size) { LOG_WARN("JPEG compression requires pixbuf data (%s)\n", i->path); exit(-1); return 0; } if (!quality) quality = DEFAULT_JPEG_QUALITY; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); image_jpeg_buf_dest(&cinfo, &dst); cinfo.image_width = i->width; cinfo.image_height = i->height; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; // output is always RGB even if source was grayscale if (setjmp(setjmp_buffer)) { if (data != NULL) { LOG_MEM("destroy JPEG data row @ %p\n", data); free((void *)data); } return 0; } #ifdef JCS_EXTENSIONS // Use libjpeg-turbo support for direct reading from source buffer cinfo.input_components = 4; cinfo.in_color_space = JCS_EXT_XBGR; #endif jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE); jpeg_start_compress(&cinfo, TRUE); #ifdef JCS_EXTENSIONS data = (JSAMPROW *)malloc(i->height * sizeof(JSAMPROW)); LOG_MEM("new JPEG data row @ %p\n", data); for (x = 0; x < i->height; x++) data[x] = (JSAMPROW)&i->_pixbuf[x * i->width]; while (cinfo.next_scanline < cinfo.image_height) { jpeg_write_scanlines(&cinfo, &data[cinfo.next_scanline], cinfo.image_height - cinfo.next_scanline); } #else // Normal libjpeg row_stride = cinfo.image_width * 3; data = (unsigned char *)malloc(row_stride); LOG_MEM("new JPEG data row @ %p\n", data); y = 0; while (cinfo.next_scanline < cinfo.image_height) { for (x = 0; x < cinfo.image_width; x++) { data[x + x + x] = COL_RED(i->_pixbuf[y]); data[x + x + x + 1] = COL_GREEN(i->_pixbuf[y]); data[x + x + x + 2] = COL_BLUE(i->_pixbuf[y]); y++; } row_pointer[0] = (unsigned char *)data; jpeg_write_scanlines(&cinfo, row_pointer, 1); } #endif jpeg_finish_compress(&cinfo); LOG_MEM("destroy JPEG data row @ %p\n", data); free((void *)data); jpeg_destroy_compress(&cinfo); // Attach compressed buffer to image i->_dbuf = (void *)dst.dbuf; return 1; }