# -*- Mode: Pyrex -*- # A Pyrex interface to libjpeg. # http://www.nightmare.com/~rushing/ # TODO: # support grayscale output # check for leaks # bad input files, etc... # handle aborting # exception handlers in manager-forwarders cdef extern from "sys/types.h": ctypedef unsigned long size_t cdef extern from "string.h": void * memcpy (void * dst, void * src, size_t len) cdef extern from "Python.h": char* PyString_AS_STRING (object) char* PyString_AsString (object) int PyString_Check (object) object PyString_FromString (char *str) object PyString_FromStringAndSize (char * s, int len) int PyString_Size (object) except -1 int PyString_GET_SIZE (object) # this is just so we can adjust ob_size manually ctypedef struct PyStringObject: int ob_size cdef extern from "jpeglib.h": ctypedef unsigned int JDIMENSION ctypedef int boolean ctypedef char JOCTET ctypedef unsigned char JSAMPLE ctypedef JSAMPLE * JSAMPROW ctypedef JSAMPROW * JSAMPARRAY # Known color spaces. cdef enum J_COLOR_SPACE: JCS_UNKNOWN, # error/unspecified JCS_GRAYSCALE, # monochrome JCS_RGB, # red/green/blue JCS_YCbCr, # Y/Cb/Cr (also known as YUV) JCS_CMYK, # C/M/Y/K JCS_YCCK # Y/Cb/Cr/K # DCT/IDCT algorithm options. cdef enum J_DCT_METHOD: JDCT_ISLOW, # slow but accurate integer algorithm JDCT_IFAST, # faster, less accurate integer method JDCT_FLOAT # floating-point: accurate, fast on fast HW # Dithering options for decompression. cdef enum J_DITHER_MODE: JDITHER_NONE, # no dithering JDITHER_ORDERED,# simple ordered dither JDITHER_FS # Floyd-Steinberg error diffusion dither cdef struct jpeg_error_mgr: int msg_code cdef struct jpeg_source_mgr cdef struct jpeg_destination_mgr cdef struct jpeg_compress_struct: jpeg_error_mgr * err void * client_data jpeg_destination_mgr * dest JDIMENSION image_width JDIMENSION image_height JDIMENSION next_scanline int input_components J_COLOR_SPACE in_color_space int data_precision int num_components J_COLOR_SPACE jpeg_color_space cdef struct jpeg_decompress_struct: jpeg_error_mgr * err void * client_data jpeg_source_mgr * src JDIMENSION image_width JDIMENSION image_height int input_components J_COLOR_SPACE jpeg_color_space J_COLOR_SPACE out_color_space unsigned int scale_num, scale_denom double output_gamma boolean buffered_image boolean raw_data_out J_DCT_METHOD dct_method boolean do_fancy_upsampling boolean do_block_smoothing boolean quantize_colors int data_precision J_DITHER_MODE dither_mode boolean two_pass_quantize int desired_number_of_colors JDIMENSION output_width JDIMENSION output_height JDIMENSION output_scanline # output_height - 1 int out_color_components int output_components int rec_outbuf_height int actual_number_of_colors cdef struct jpeg_source_mgr: JOCTET * next_input_byte size_t bytes_in_buffer void (*init_source) (jpeg_decompress_struct *) boolean (*fill_input_buffer) (jpeg_decompress_struct *) void (*skip_input_data) (jpeg_decompress_struct *, long num_bytes) boolean (*resync_to_restart) (jpeg_decompress_struct *, int desired) void (*term_source) (jpeg_decompress_struct *) cdef struct jpeg_destination_mgr: JOCTET * next_output_byte size_t free_in_buffer void (*init_destination) (jpeg_compress_struct *) boolean (*empty_output_buffer) (jpeg_compress_struct *) void (*term_destination) (jpeg_compress_struct *) cdef void jpeg_create_compress (jpeg_compress_struct *) cdef void jpeg_create_decompress (jpeg_decompress_struct *) cdef jpeg_error_mgr * jpeg_std_error (jpeg_error_mgr *) cdef int jpeg_read_header (jpeg_decompress_struct * cinfo, boolean require_image) cdef void jpeg_set_colorspace (jpeg_compress_struct *, J_COLOR_SPACE) cdef void jpeg_set_defaults (jpeg_compress_struct *) cdef void jpeg_set_quality (jpeg_compress_struct *, int quality, boolean force_baseline) cdef void jpeg_start_compress (jpeg_compress_struct *, boolean write_all_tables) cdef void jpeg_finish_compress (jpeg_compress_struct *) cdef void jpeg_simple_progression (jpeg_compress_struct *) cdef boolean jpeg_start_decompress (jpeg_decompress_struct *) cdef boolean jpeg_finish_decompress (jpeg_decompress_struct *) cdef JDIMENSION jpeg_read_scanlines (jpeg_decompress_struct *, JSAMPARRAY scanlines, JDIMENSION max_lines) cdef JDIMENSION jpeg_write_scanlines (jpeg_compress_struct *, JSAMPARRAY scanlines, JDIMENSION num_lines) cdef void jpeg_destroy_compress (jpeg_compress_struct *) cdef void jpeg_destroy_decompress (jpeg_decompress_struct *) cdef class decompressor cdef class compressor # these just forward the 'source' methods to the decompression object. # XXX they need exception handlers cdef void init_source (jpeg_decompress_struct * cinfo): cdef decompressor decomp decomp = cinfo.client_data decomp.init_source() cdef boolean fill_input_buffer (jpeg_decompress_struct * cinfo): cdef decompressor decomp decomp = cinfo.client_data return decomp.fill_input_buffer() cdef void skip_input_data (jpeg_decompress_struct * cinfo, long num_bytes): cdef decompressor decomp decomp = cinfo.client_data decomp.skip_input_data (num_bytes) cdef boolean resync_to_restart (jpeg_decompress_struct * cinfo, int desired): cdef decompressor decomp decomp = cinfo.client_data return decomp.resync_to_restart (desired) cdef void term_source (jpeg_decompress_struct * cinfo): cdef decompressor decomp decomp = cinfo.client_data decomp.term_source() cdef void init_destination (jpeg_compress_struct * cinfo): cdef compressor comp comp = cinfo.client_data comp.init_destination() cdef boolean empty_output_buffer (jpeg_compress_struct * cinfo): cdef compressor comp comp = cinfo.client_data return comp.empty_output_buffer() cdef void term_destination (jpeg_compress_struct * cinfo): cdef compressor comp comp = cinfo.client_data comp.term_destination() import sys W = sys.stderr.write cdef class decompressor: cdef jpeg_decompress_struct cinfo cdef jpeg_error_mgr jerr cdef jpeg_source_mgr src cdef object current_buffer cdef int current_buffer_size cdef boolean header_read def __init__ (self): self.cinfo.client_data = self self.cinfo.err = jpeg_std_error (&(self.jerr)) jpeg_create_decompress (&self.cinfo) self.init_source_mgr() self.current_buffer = '' self.current_buffer_size = 0 self.header_read = 0 def __dealloc__ (self): jpeg_destroy_decompress (&self.cinfo) cdef void init_source_mgr (self): self.src.init_source = init_source self.src.fill_input_buffer = fill_input_buffer self.src.skip_input_data = skip_input_data self.src.resync_to_restart = resync_to_restart self.src.term_source = term_source self.cinfo.src = &self.src cdef void init_source (self): #print 'init_source() NYI' pass cdef boolean fill_input_buffer (self): self.current_buffer = self.get_more_data() self.current_buffer_size = PyString_Size (self.current_buffer) self.src.bytes_in_buffer = self.current_buffer_size self.src.next_input_byte = PyString_AS_STRING (self.current_buffer) return 1 cdef void skip_input_data (self, long num_bytes): cdef long skip if num_bytes <= 0: pass elif num_bytes > self.src.bytes_in_buffer: skip = num_bytes - self.src.bytes_in_buffer self.fill_input_buffer() self.skip_input_data (skip) else: self.src.bytes_in_buffer = self.src.bytes_in_buffer - num_bytes self.src.next_input_byte = self.src.next_input_byte + num_bytes cdef boolean resync_to_restart (self, int desired): print 'resync_to_restart() NYI' cdef void term_source (self): #print 'term_source() NYI' pass def image_size (self): return self.cinfo.image_width, self.cinfo.image_height def output_size (self): return self.cinfo.output_width, self.cinfo.output_height def dump (self): print 'image size: %d x %d' % self.image_size() print 'output size: %d x %d' % self.output_size() print 'DCT method: %d' % self.cinfo.dct_method print 'color space: %d' % self.cinfo.jpeg_color_space def read_header (self): if not self.header_read: self.header_read = 1 jpeg_read_header (&self.cinfo, 1) return self.cinfo.image_width, self.cinfo.image_height def go (self, int scale=1): cdef object buffer cdef int n, buffer_height cdef JSAMPROW scanline[1] self.read_header() # set scale, color/gray, etc... here # allocate buffer - we make room for a single scanline buffer_height = 1 self.cinfo.out_color_space = JCS_RGB self.cinfo.scale_num = 1 self.cinfo.scale_denom = scale jpeg_start_decompress (&self.cinfo) # Note: this assumes 8-bit JSAMPLE size buffer_size = self.cinfo.output_width * self.cinfo.output_components * buffer_height while self.cinfo.output_scanline < self.cinfo.output_height: buffer = PyString_FromStringAndSize (NULL, buffer_size) scanline[0] = PyString_AS_STRING (buffer) n = jpeg_read_scanlines ( &self.cinfo, &scanline[0], buffer_height ) self.put_scanline (buffer, n) # done jpeg_finish_decompress (&self.cinfo) class file_decompressor (decompressor): def __init__ (self, file): decompressor.__init__ (self) self.file = file self.scanlines = [] def get_more_data (self): return self.file.read (16384) def put_scanline (self, data, n): self.scanlines.append (data) cdef class compressor: cdef jpeg_compress_struct cinfo cdef jpeg_error_mgr jerr cdef jpeg_destination_mgr dest cdef object buffer cdef int buffer_size def __init__ (self, int image_width, int image_height, int buffer_size=8192): self.cinfo.client_data = self self.cinfo.err = jpeg_std_error (&(self.jerr)) jpeg_create_compress (&self.cinfo) self.init_destination_mgr() self.buffer_size = buffer_size self.cinfo.image_width = image_width self.cinfo.image_height = image_height # for now, we'll hard-code the 8-bit RGB assumption self.cinfo.input_components = 3 self.cinfo.in_color_space = JCS_RGB jpeg_set_defaults (&self.cinfo) def __dealloc__ (self): jpeg_destroy_compress (&self.cinfo) def set_quality (self, int quality): jpeg_set_quality (&self.cinfo, quality, 1) def set_grayscale (self): jpeg_set_colorspace (&self.cinfo, JCS_GRAYSCALE) def simple_progression (self): jpeg_simple_progression (&self.cinfo) cdef void init_destination_mgr (self): self.dest.init_destination = init_destination self.dest.term_destination = term_destination self.dest.empty_output_buffer = empty_output_buffer self.cinfo.dest = &self.dest cdef void init_destination (self): #W ('[id]') self.buffer = PyString_FromStringAndSize (NULL, self.buffer_size) self.dest.next_output_byte = PyString_AS_STRING (self.buffer) self.dest.free_in_buffer = self.buffer_size cdef void term_destination (self): #W ('[td]') if self.dest.free_in_buffer < self.buffer_size: self.write_buffer (self.buffer, self.buffer_size - self.dest.free_in_buffer) self.buffer = None cdef boolean empty_output_buffer (self): #W ('[eb]') # virtual self.write_buffer (self.buffer, self.buffer_size) # reset the buffer pointers self.dest.next_output_byte = PyString_AS_STRING (self.buffer) self.dest.free_in_buffer = self.buffer_size return 1 cdef _get_scanline (self, int size): raise NotImplementedError def go (self): cdef int n, buffer_height cdef JSAMPROW scanline[1] jpeg_start_compress (&self.cinfo, 1) # Note: this assumes 8-bit JSAMPLE size buffer_height = 1 buffer_size = self.cinfo.image_width * self.cinfo.input_components * buffer_height while self.cinfo.next_scanline < self.cinfo.image_height: #W ('(%d)' % (self.cinfo.next_scanline,)) buffer = self._get_scanline (buffer_size) if (PyString_Size (buffer) < buffer_size): W ('expected: %d got: %d\n' % (PyString_Size (buffer), buffer_size)) raise ValueError, "get_scanline() must return a string of the given size" else: scanline[0] = PyString_AS_STRING (buffer) jpeg_write_scanlines ( &self.cinfo, &scanline[0], buffer_height ) # done jpeg_finish_compress (&self.cinfo) cdef class file_compressor (compressor): cdef object file cdef object scanlines cdef int index def __init__ (self, file, int width, int height, scanlines): compressor.__init__ (self, width, height) self.file = file self.scanlines = scanlines self.index = 0 def write_buffer (self, buffer, size): return self.file.write (buffer[:size]) cdef _get_scanline (self, int size): line = self.scanlines[self.index] self.index = self.index + 1 #assert (len(line) == size) return line def get_scanline (self, size): line = self.scanlines[self.index] self.index = self.index + 1 #assert (len(line) == size) return line