/* Copyright (C) 2009 Red Hat, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #define DJB2_START 5381 #define DJB2_HASH(hash, c) (hash = ((hash << 5) + hash) ^ (c)) //|{hash = ((hash << 5) + hash) + c;} /* For each pixel type the following macros are defined: PIXEL : input type FNAME(name) ENCODE_PIXEL(encoder, pixel) : writing a pixel to the compressed buffer (byte by byte) SAME_PIXEL(pix1, pix2) : comparing two pixels HASH_FUNC(value, pix_ptr) : hash func of 3 consecutive pixels */ #ifdef LZ_PLT #define PIXEL one_byte_pixel_t #define FNAME(name) glz_plt_##name #define ENCODE_PIXEL(e, pix) encode(e, (pix).a) // gets the pixel and write only the needed bytes // from the pixel #define SAME_PIXEL(pix1, pix2) ((pix1).a == (pix2).a) #define MIN_REF_ENCODE_SIZE 4 #define MAX_REF_ENCODE_SIZE 7 #define HASH_FUNC(v, p) { \ v = DJB2_START; \ DJB2_HASH(v, p[0].a); \ DJB2_HASH(v, p[1].a); \ DJB2_HASH(v, p[2].a); \ v &= HASH_MASK; \ } #endif #ifdef LZ_RGB_ALPHA //#undef LZ_RGB_ALPHA #define PIXEL rgb32_pixel_t #define FNAME(name) glz_rgb_alpha_##name #define ENCODE_PIXEL(e, pix) {encode(e, (pix).pad);} #define SAME_PIXEL(pix1, pix2) ((pix1).pad == (pix2).pad) #define MIN_REF_ENCODE_SIZE 4 #define MAX_REF_ENCODE_SIZE 7 #define HASH_FUNC(v, p) { \ v = DJB2_START; \ DJB2_HASH(v, p[0].pad); \ DJB2_HASH(v, p[1].pad); \ DJB2_HASH(v, p[2].pad); \ v &= HASH_MASK; \ } #endif #ifdef LZ_RGB16 #define PIXEL rgb16_pixel_t #define FNAME(name) glz_rgb16_##name #define GET_r(pix) (((pix) >> 10) & 0x1f) #define GET_g(pix) (((pix) >> 5) & 0x1f) #define GET_b(pix) ((pix) & 0x1f) #define ENCODE_PIXEL(e, pix) {encode(e, (pix) >> 8); encode(e, (pix) & 0xff);} #define MIN_REF_ENCODE_SIZE 2 #define MAX_REF_ENCODE_SIZE 3 #define HASH_FUNC(v, p) { \ v = DJB2_START; \ DJB2_HASH(v, p[0] & (0x00ff)); \ DJB2_HASH(v, (p[0] >> 8) & (0x007f)); \ DJB2_HASH(v, p[1] & (0x00ff)); \ DJB2_HASH(v, (p[1] >> 8) & (0x007f)); \ DJB2_HASH(v, p[2] & (0x00ff)); \ DJB2_HASH(v, (p[2] >> 8) & (0x007f)); \ v &= HASH_MASK; \ } #endif #ifdef LZ_RGB24 #define PIXEL rgb24_pixel_t #define FNAME(name) glz_rgb24_##name #define ENCODE_PIXEL(e, pix) {encode(e, (pix).b); encode(e, (pix).g); encode(e, (pix).r);} #define MIN_REF_ENCODE_SIZE 2 #define MAX_REF_ENCODE_SIZE 2 #endif #ifdef LZ_RGB32 #define PIXEL rgb32_pixel_t #define FNAME(name) glz_rgb32_##name #define ENCODE_PIXEL(e, pix) {encode(e, (pix).b); encode(e, (pix).g); encode(e, (pix).r);} #define MIN_REF_ENCODE_SIZE 2 #define MAX_REF_ENCODE_SIZE 2 #endif #if defined(LZ_RGB24) || defined(LZ_RGB32) #define GET_r(pix) ((pix).r) #define GET_g(pix) ((pix).g) #define GET_b(pix) ((pix).b) #define HASH_FUNC(v, p) { \ v = DJB2_START; \ DJB2_HASH(v, p[0].r); \ DJB2_HASH(v, p[0].g); \ DJB2_HASH(v, p[0].b); \ DJB2_HASH(v, p[1].r); \ DJB2_HASH(v, p[1].g); \ DJB2_HASH(v, p[1].b); \ DJB2_HASH(v, p[2].r); \ DJB2_HASH(v, p[2].g); \ DJB2_HASH(v, p[2].b); \ v &= HASH_MASK; \ } #endif #if defined(LZ_RGB16) || defined(LZ_RGB24) || defined(LZ_RGB32) #define SAME_PIXEL(p1, p2) (GET_r(p1) == GET_r(p2) && GET_g(p1) == GET_g(p2) && \ GET_b(p1) == GET_b(p2)) #endif #ifndef LZ_PLT #define PIXEL_ID(pix_ptr, seg_ptr) \ ((pix_ptr) - ((PIXEL *)(seg_ptr)->lines) + (seg_ptr)->pixels_so_far) #define PIXEL_DIST(src_pix_ptr, src_seg_ptr, ref_pix_ptr, ref_seg_ptr) \ (PIXEL_ID(src_pix_ptr,src_seg_ptr) - PIXEL_ID(ref_pix_ptr, ref_seg_ptr)) #else #define PIXEL_ID(pix_ptr, seg_ptr, pix_per_byte) \ (((pix_ptr) - ((PIXEL *)(seg_ptr)->lines)) * pix_per_byte + (seg_ptr)->pixels_so_far) #define PIXEL_DIST(src_pix_ptr, src_seg_ptr, ref_pix_ptr, ref_seg_ptr, pix_per_byte) \ ((PIXEL_ID(src_pix_ptr,src_seg_ptr, pix_per_byte) - \ PIXEL_ID(ref_pix_ptr, ref_seg_ptr, pix_per_byte)) / pix_per_byte) #endif /* returns the length of the match. 0 if no match. if image_distance = 0, pixel_distance is the distance between the matching pixels. Otherwise, it is the offset from the beginning of the referred image */ static inline size_t FNAME(do_match)(SharedDictionary *dict, WindowImageSegment *ref_seg, const PIXEL *ref, const PIXEL *ref_limit, WindowImageSegment *ip_seg, const PIXEL *ip, const PIXEL *ip_limit, #ifdef LZ_PLT int pix_per_byte, #endif size_t *o_image_dist, size_t *o_pix_distance) { int encode_size; const PIXEL *tmp_ip = ip; const PIXEL *tmp_ref = ref; if (ref > (ref_limit - MIN_REF_ENCODE_SIZE)) { return 0; // in case the hash entry is not relevant } /* min match length == MIN_REF_ENCODE_SIZE (depends on pixel type) */ if (!SAME_PIXEL(*tmp_ref, *tmp_ip)) { return 0; } else { tmp_ref++; tmp_ip++; } if (!SAME_PIXEL(*tmp_ref, *tmp_ip)) { return 0; } else { tmp_ref++; tmp_ip++; } #if defined(LZ_PLT) || defined(LZ_RGB_ALPHA) if (!SAME_PIXEL(*tmp_ref, *tmp_ip)) { return 0; } else { tmp_ref++; tmp_ip++; } if (!SAME_PIXEL(*tmp_ref, *tmp_ip)) { return 0; } else { tmp_ref++; tmp_ip++; } #endif *o_image_dist = ip_seg->image->id - ref_seg->image->id; if (!(*o_image_dist)) { // the ref is inside the same image - encode distance #ifndef LZ_PLT *o_pix_distance = PIXEL_DIST(ip, ip_seg, ref, ref_seg); #else // in bytes *o_pix_distance = PIXEL_DIST(ip, ip_seg, ref, ref_seg, pix_per_byte); #endif } else { // the ref is at different image - encode offset from the image start #ifndef LZ_PLT *o_pix_distance = PIXEL_DIST(ref, ref_seg, (PIXEL *)(dict->window.segs[ref_seg->image->first_seg].lines), &dict->window.segs[ref_seg->image->first_seg] ); #else // in bytes *o_pix_distance = PIXEL_DIST(ref, ref_seg, (PIXEL *)(dict->window.segs[ref_seg->image->first_seg].lines), &dict->window.segs[ref_seg->image->first_seg], pix_per_byte); #endif } if ((*o_pix_distance == 0) || (*o_pix_distance >= MAX_PIXEL_LONG_DISTANCE) || (*o_image_dist > MAX_IMAGE_DIST)) { return 0; } /* continue the match*/ while ((tmp_ip < ip_limit) && (tmp_ref < ref_limit)) { if (!SAME_PIXEL(*tmp_ref, *tmp_ip)) { break; } else { tmp_ref++; tmp_ip++; } } if ((tmp_ip - ip) > MAX_REF_ENCODE_SIZE) { return (tmp_ip - ip); } encode_size = get_encode_ref_size(*o_image_dist, *o_pix_distance); // min number of identical pixels for a match #if defined(LZ_RGB16) encode_size /= 2; #elif defined(LZ_RGB24) || defined(LZ_RGB32) encode_size /= 3; #endif encode_size++; // the minimum match // match len is smaller than the encoding - not worth encoding if ((tmp_ip - ip) < encode_size) { return 0; } return (tmp_ip - ip); } /* compresses one segment starting from 'from'. In order to encode a match, we use pixels resolution when we encode RGB image, and bytes count when we encode PLT. */ static void FNAME(compress_seg)(Encoder *encoder, uint32_t seg_idx, PIXEL *from, int copied) { WindowImageSegment *seg = &encoder->dict->window.segs[seg_idx]; const PIXEL *ip = from; const PIXEL *ip_bound = (PIXEL *)(seg->lines_end) - BOUND_OFFSET; const PIXEL *ip_limit = (PIXEL *)(seg->lines_end) - LIMIT_OFFSET; int hval; int copy = copied; #ifdef LZ_PLT int pix_per_byte = PLT_PIXELS_PER_BYTE[encoder->cur_image.type]; #endif #ifdef DEBUG_ENCODE int n_encoded = 0; #endif if (copy == 0) { encode_copy_count(encoder, MAX_COPY - 1); } while (LZ_EXPECT_CONDITIONAL(ip < ip_limit)) { const PIXEL *ref; const PIXEL *ref_limit; WindowImageSegment *ref_seg; uint32_t ref_seg_idx; size_t pix_dist; size_t image_dist; /* minimum match length */ size_t len = 0; /* comparison starting-point */ const PIXEL *anchor = ip; #ifdef CHAINED_HASH int hash_id = 0; size_t best_len = 0; size_t best_pix_dist = 0; size_t best_image_dist = 0; #endif /* check for a run */ if (LZ_EXPECT_CONDITIONAL(ip > (PIXEL *)(seg->lines))) { if (SAME_PIXEL(ip[-1], ip[0]) && SAME_PIXEL(ip[0], ip[1]) && SAME_PIXEL(ip[1], ip[2])) { PIXEL x; pix_dist = 1; image_dist = 0; ip += 3; ref = anchor + 2; ref_limit = (PIXEL *)(seg->lines_end); len = 3; x = *ref; while (ip < ip_bound) { // TODO: maybe separate a run from the same seg or from // different ones in order to spare ref < ref_limit if (!SAME_PIXEL(*ip, x)) { ip++; break; } else { ip++; len++; } } goto match; } // END RLE MATCH } /* find potential match */ HASH_FUNC(hval, ip); #ifdef CHAINED_HASH for (hash_id = 0; hash_id < HASH_CHAIN_SIZE; hash_id++) { ref_seg_idx = encoder->dict->htab[hval][hash_id].image_seg_idx; #else ref_seg_idx = encoder->dict->htab[hval].image_seg_idx; #endif ref_seg = encoder->dict->window.segs + ref_seg_idx; if (REF_SEG_IS_VALID(encoder->dict, encoder->id, ref_seg, seg)) { #ifdef CHAINED_HASH ref = ((PIXEL *)ref_seg->lines) + encoder->dict->htab[hval][hash_id].ref_pix_idx; #else ref = ((PIXEL *)ref_seg->lines) + encoder->dict->htab[hval].ref_pix_idx; #endif ref_limit = (PIXEL *)ref_seg->lines_end; len = FNAME(do_match)(encoder->dict, ref_seg, ref, ref_limit, seg, ip, ip_bound, #ifdef LZ_PLT pix_per_byte, #endif &image_dist, &pix_dist); #ifdef CHAINED_HASH // TODO. not compare len but rather len - encode_size if (len > best_len) { best_len = len; best_pix_dist = pix_dist; best_image_dist = image_dist; } #endif } #ifdef CHAINED_HASH } // end chain loop len = best_len; pix_dist = best_pix_dist; image_dist = best_image_dist; #endif /* update hash table */ UPDATE_HASH(encoder->dict, hval, seg_idx, anchor - ((PIXEL *)seg->lines)); if (!len) { goto literal; } match: // RLE or dictionary (both are encoded by distance from ref (-1) and length) #ifdef DEBUG_ENCODE printf(", match(%d, %d, %d)", image_dist, pix_dist, len); n_encoded += len; #endif /* distance is biased */ if (!image_dist) { pix_dist--; } /* if we have copied something, adjust the copy count */ if (copy) { /* copy is biased, '0' means 1 byte copy */ update_copy_count(encoder, copy - 1); } else { /* back, to overwrite the copy count */ compress_output_prev(encoder); } /* reset literal counter */ copy = 0; /* length is biased, '1' means a match of 3 pixels for PLT and alpha*/ /* for RGB 16 1 means 2 */ /* for RGB24/32 1 means 1...*/ ip = anchor + len - 2; #if defined(LZ_RGB16) len--; #elif defined(LZ_PLT) || defined(LZ_RGB_ALPHA) len -= 2; #endif GLZ_ASSERT(encoder->usr, len > 0); encode_match(encoder, image_dist, pix_dist, len); /* update the hash at match boundary */ #if defined(LZ_RGB16) || defined(LZ_RGB24) || defined(LZ_RGB32) if (ip > anchor) { #endif HASH_FUNC(hval, ip); UPDATE_HASH(encoder->dict, hval, seg_idx, ip - ((PIXEL *)seg->lines)); ip++; #if defined(LZ_RGB16) || defined(LZ_RGB24) || defined(LZ_RGB32) } else {ip++; } #endif #if defined(LZ_RGB24) || defined(LZ_RGB32) if (ip > anchor) { #endif HASH_FUNC(hval, ip); UPDATE_HASH(encoder->dict, hval, seg_idx, ip - ((PIXEL *)seg->lines)); ip++; #if defined(LZ_RGB24) || defined(LZ_RGB32) } else { ip++; } #endif /* assuming literal copy */ encode_copy_count(encoder, MAX_COPY - 1); continue; literal: #ifdef DEBUG_ENCODE printf(", copy"); n_encoded++; #endif ENCODE_PIXEL(encoder, *anchor); anchor++; ip = anchor; copy++; if (LZ_UNEXPECT_CONDITIONAL(copy == MAX_COPY)) { copy = 0; encode_copy_count(encoder, MAX_COPY - 1); } } // END LOOP (ip < ip_limit) /* left-over as literal copy */ ip_bound++; while (ip <= ip_bound) { #ifdef DEBUG_ENCODE printf(", copy"); n_encoded++; #endif ENCODE_PIXEL(encoder, *ip); ip++; copy++; if (copy == MAX_COPY) { copy = 0; encode_copy_count(encoder, MAX_COPY - 1); } } /* if we have copied something, adjust the copy length */ if (copy) { update_copy_count(encoder, copy - 1); } else { compress_output_prev(encoder); } #ifdef DEBUG_ENCODE printf("\ntotal encoded=%d\n", n_encoded); #endif } /* If the file is very small, copies it. copies the first two pixels of the first segment, and sends the segments one by one to compress_seg. the number of bytes compressed are stored inside encoder. */ static void FNAME(compress)(Encoder *encoder) { uint32_t seg_id = encoder->cur_image.first_win_seg; PIXEL *ip; SharedDictionary *dict = encoder->dict; int hval; // fetch the first image segment that is not too small while ((seg_id != NULL_IMAGE_SEG_ID) && (dict->window.segs[seg_id].image->id == encoder->cur_image.id) && ((((PIXEL *)dict->window.segs[seg_id].lines_end) - ((PIXEL *)dict->window.segs[seg_id].lines)) < 4)) { // coping the segment if (dict->window.segs[seg_id].lines != dict->window.segs[seg_id].lines_end) { ip = (PIXEL *)dict->window.segs[seg_id].lines; // Note: we assume MAX_COPY > 3 encode_copy_count(encoder, (uint8_t)( (((PIXEL *)dict->window.segs[seg_id].lines_end) - ((PIXEL *)dict->window.segs[seg_id].lines)) - 1)); while (ip < (PIXEL *)dict->window.segs[seg_id].lines_end) { ENCODE_PIXEL(encoder, *ip); ip++; } } seg_id = dict->window.segs[seg_id].next; } if ((seg_id == NULL_IMAGE_SEG_ID) || (dict->window.segs[seg_id].image->id != encoder->cur_image.id)) { return; } ip = (PIXEL *)dict->window.segs[seg_id].lines; encode_copy_count(encoder, MAX_COPY - 1); HASH_FUNC(hval, ip); UPDATE_HASH(encoder->dict, hval, seg_id, 0); ENCODE_PIXEL(encoder, *ip); ip++; ENCODE_PIXEL(encoder, *ip); ip++; #ifdef DEBUG_ENCODE printf("copy, copy"); #endif // compressing the first segment FNAME(compress_seg)(encoder, seg_id, ip, 2); // compressing the next segments for (seg_id = dict->window.segs[seg_id].next; seg_id != NULL_IMAGE_SEG_ID && ( dict->window.segs[seg_id].image->id == encoder->cur_image.id); seg_id = dict->window.segs[seg_id].next) { FNAME(compress_seg)(encoder, seg_id, (PIXEL *)dict->window.segs[seg_id].lines, 0); } } #undef FNAME #undef PIXEL_ID #undef PIXEL_DIST #undef PIXEL #undef ENCODE_PIXEL #undef SAME_PIXEL #undef HASH_FUNC #undef GET_r #undef GET_g #undef GET_b #undef GET_CODE #undef LZ_PLT #undef LZ_RGB_ALPHA #undef LZ_RGB16 #undef LZ_RGB24 #undef LZ_RGB32 #undef MIN_REF_ENCODE_SIZE #undef MAX_REF_ENCODE_SIZE