Pillow
546 строк · 15.6 Кб
1/* Copyright (c) 2010 Oliver Tonnhofer <olt@bogosoft.com>, Omniscale
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20*/
21
22/*
23// This file implements a variation of the octree color quantization algorithm.
24*/
25
26#include <stdio.h>27#include <stdlib.h>28#include <string.h>29#include <limits.h>30
31#include "ImagingUtils.h"32#include "QuantOctree.h"33
34typedef struct _ColorBucket {35/* contains palette index when used for look up cube */36uint32_t count;37uint64_t r;38uint64_t g;39uint64_t b;40uint64_t a;41} *ColorBucket;42
43typedef struct _ColorCube {44unsigned int rBits, gBits, bBits, aBits;45unsigned int rWidth, gWidth, bWidth, aWidth;46unsigned int rOffset, gOffset, bOffset, aOffset;47
48unsigned long size;49ColorBucket buckets;50} *ColorCube;51
52#define MAX(a, b) (a) > (b) ? (a) : (b)53
54static ColorCube55new_color_cube(int r, int g, int b, int a) {56ColorCube cube;57
58/* malloc check ok, small constant allocation */59cube = malloc(sizeof(struct _ColorCube));60if (!cube) {61return NULL;62}63
64cube->rBits = MAX(r, 0);65cube->gBits = MAX(g, 0);66cube->bBits = MAX(b, 0);67cube->aBits = MAX(a, 0);68
69/* overflow check for size multiplication below */70if (cube->rBits + cube->gBits + cube->bBits + cube->aBits > 31) {71free(cube);72return NULL;73}74
75/* the width of the cube for each dimension */76cube->rWidth = 1 << cube->rBits;77cube->gWidth = 1 << cube->gBits;78cube->bWidth = 1 << cube->bBits;79cube->aWidth = 1 << cube->aBits;80
81/* the offsets of each color */82
83cube->rOffset = cube->gBits + cube->bBits + cube->aBits;84cube->gOffset = cube->bBits + cube->aBits;85cube->bOffset = cube->aBits;86cube->aOffset = 0;87
88/* the number of color buckets */89cube->size = cube->rWidth * cube->gWidth * cube->bWidth * cube->aWidth;90/* malloc check ok, overflow checked above */91cube->buckets = calloc(cube->size, sizeof(struct _ColorBucket));92
93if (!cube->buckets) {94free(cube);95return NULL;96}97return cube;98}
99
100static void101free_color_cube(ColorCube cube) {102if (cube != NULL) {103free(cube->buckets);104free(cube);105}106}
107
108static long109color_bucket_offset_pos(110const ColorCube cube, unsigned int r, unsigned int g, unsigned int b, unsigned int a111) {112return r << cube->rOffset | g << cube->gOffset | b << cube->bOffset |113a << cube->aOffset;114}
115
116static long117color_bucket_offset(const ColorCube cube, const Pixel *p) {118unsigned int r = p->c.r >> (8 - cube->rBits);119unsigned int g = p->c.g >> (8 - cube->gBits);120unsigned int b = p->c.b >> (8 - cube->bBits);121unsigned int a = p->c.a >> (8 - cube->aBits);122return color_bucket_offset_pos(cube, r, g, b, a);123}
124
125static ColorBucket126color_bucket_from_cube(const ColorCube cube, const Pixel *p) {127unsigned int offset = color_bucket_offset(cube, p);128return &cube->buckets[offset];129}
130
131static void132add_color_to_color_cube(const ColorCube cube, const Pixel *p) {133ColorBucket bucket = color_bucket_from_cube(cube, p);134bucket->count += 1;135bucket->r += p->c.r;136bucket->g += p->c.g;137bucket->b += p->c.b;138bucket->a += p->c.a;139}
140
141static unsigned long142count_used_color_buckets(const ColorCube cube) {143unsigned long usedBuckets = 0;144unsigned long i;145for (i = 0; i < cube->size; i++) {146if (cube->buckets[i].count > 0) {147usedBuckets += 1;148}149}150return usedBuckets;151}
152
153static void154avg_color_from_color_bucket(const ColorBucket bucket, Pixel *dst) {155float count = bucket->count;156if (count != 0) {157dst->c.r = CLIP8((int)(bucket->r / count));158dst->c.g = CLIP8((int)(bucket->g / count));159dst->c.b = CLIP8((int)(bucket->b / count));160dst->c.a = CLIP8((int)(bucket->a / count));161} else {162dst->c.r = 0;163dst->c.g = 0;164dst->c.b = 0;165dst->c.a = 0;166}167}
168
169static int170compare_bucket_count(const ColorBucket a, const ColorBucket b) {171return b->count - a->count;172}
173
174static ColorBucket175create_sorted_color_palette(const ColorCube cube) {176ColorBucket buckets;177if (cube->size > LONG_MAX / sizeof(struct _ColorBucket)) {178return NULL;179}180/* malloc check ok, calloc + overflow check above for memcpy */181buckets = calloc(cube->size, sizeof(struct _ColorBucket));182if (!buckets) {183return NULL;184}185memcpy(buckets, cube->buckets, sizeof(struct _ColorBucket) * cube->size);186
187qsort(188buckets,189cube->size,190sizeof(struct _ColorBucket),191(int (*)(void const *, void const *)) & compare_bucket_count192);193
194return buckets;195}
196
197void
198add_bucket_values(ColorBucket src, ColorBucket dst) {199dst->count += src->count;200dst->r += src->r;201dst->g += src->g;202dst->b += src->b;203dst->a += src->a;204}
205
206/* expand or shrink a given cube to level */
207static ColorCube208copy_color_cube(209const ColorCube cube,210unsigned int rBits,211unsigned int gBits,212unsigned int bBits,213unsigned int aBits214) {215unsigned int r, g, b, a;216long src_pos, dst_pos;217unsigned int src_reduce[4] = {0}, dst_reduce[4] = {0};218unsigned int width[4];219ColorCube result;220
221result = new_color_cube(rBits, gBits, bBits, aBits);222if (!result) {223return NULL;224}225
226if (cube->rBits > rBits) {227dst_reduce[0] = cube->rBits - result->rBits;228width[0] = cube->rWidth;229} else {230src_reduce[0] = result->rBits - cube->rBits;231width[0] = result->rWidth;232}233if (cube->gBits > gBits) {234dst_reduce[1] = cube->gBits - result->gBits;235width[1] = cube->gWidth;236} else {237src_reduce[1] = result->gBits - cube->gBits;238width[1] = result->gWidth;239}240if (cube->bBits > bBits) {241dst_reduce[2] = cube->bBits - result->bBits;242width[2] = cube->bWidth;243} else {244src_reduce[2] = result->bBits - cube->bBits;245width[2] = result->bWidth;246}247if (cube->aBits > aBits) {248dst_reduce[3] = cube->aBits - result->aBits;249width[3] = cube->aWidth;250} else {251src_reduce[3] = result->aBits - cube->aBits;252width[3] = result->aWidth;253}254
255for (r = 0; r < width[0]; r++) {256for (g = 0; g < width[1]; g++) {257for (b = 0; b < width[2]; b++) {258for (a = 0; a < width[3]; a++) {259src_pos = color_bucket_offset_pos(260cube,261r >> src_reduce[0],262g >> src_reduce[1],263b >> src_reduce[2],264a >> src_reduce[3]265);266dst_pos = color_bucket_offset_pos(267result,268r >> dst_reduce[0],269g >> dst_reduce[1],270b >> dst_reduce[2],271a >> dst_reduce[3]272);273add_bucket_values(274&cube->buckets[src_pos], &result->buckets[dst_pos]275);276}277}278}279}280return result;281}
282
283void
284subtract_color_buckets(ColorCube cube, ColorBucket buckets, long nBuckets) {285ColorBucket minuend, subtrahend;286long i;287Pixel p;288for (i = 0; i < nBuckets; i++) {289subtrahend = &buckets[i];290
291// If the subtrahend contains no buckets, there is nothing to subtract.292if (subtrahend->count == 0) {293continue;294}295
296avg_color_from_color_bucket(subtrahend, &p);297minuend = color_bucket_from_cube(cube, &p);298minuend->count -= subtrahend->count;299minuend->r -= subtrahend->r;300minuend->g -= subtrahend->g;301minuend->b -= subtrahend->b;302minuend->a -= subtrahend->a;303}304}
305
306static void307set_lookup_value(const ColorCube cube, const Pixel *p, long value) {308ColorBucket bucket = color_bucket_from_cube(cube, p);309bucket->count = value;310}
311
312uint64_t
313lookup_color(const ColorCube cube, const Pixel *p) {314ColorBucket bucket = color_bucket_from_cube(cube, p);315return bucket->count;316}
317
318void
319add_lookup_buckets(ColorCube cube, ColorBucket palette, long nColors, long offset) {320long i;321Pixel p;322for (i = offset + nColors - 1; i >= offset; i--) {323avg_color_from_color_bucket(&palette[i], &p);324set_lookup_value(cube, &p, i);325}326}
327
328ColorBucket
329combined_palette(330ColorBucket bucketsA,331unsigned long nBucketsA,332ColorBucket bucketsB,333unsigned long nBucketsB334) {335ColorBucket result;336if (nBucketsA > LONG_MAX - nBucketsB ||337(nBucketsA + nBucketsB) > LONG_MAX / sizeof(struct _ColorBucket)) {338return NULL;339}340/* malloc check ok, overflow check above */341result = calloc(nBucketsA + nBucketsB, sizeof(struct _ColorBucket));342if (!result) {343return NULL;344}345memcpy(result, bucketsA, sizeof(struct _ColorBucket) * nBucketsA);346memcpy(&result[nBucketsA], bucketsB, sizeof(struct _ColorBucket) * nBucketsB);347return result;348}
349
350static Pixel *351create_palette_array(const ColorBucket palette, unsigned int paletteLength) {352Pixel *paletteArray;353unsigned int i;354
355/* malloc check ok, calloc for overflow */356paletteArray = calloc(paletteLength, sizeof(Pixel));357if (!paletteArray) {358return NULL;359}360
361for (i = 0; i < paletteLength; i++) {362avg_color_from_color_bucket(&palette[i], &paletteArray[i]);363}364return paletteArray;365}
366
367static void368map_image_pixels(369const Pixel *pixelData,370uint32_t nPixels,371const ColorCube lookupCube,372uint32_t *pixelArray373) {374long i;375for (i = 0; i < nPixels; i++) {376pixelArray[i] = lookup_color(lookupCube, &pixelData[i]);377}378}
379
380const unsigned int CUBE_LEVELS[8] = {4, 4, 4, 0, 2, 2, 2, 0};381const unsigned int CUBE_LEVELS_ALPHA[8] = {3, 4, 3, 3, 2, 2, 2, 2};382
383int
384quantize_octree(385Pixel *pixelData,386uint32_t nPixels,387uint32_t nQuantPixels,388Pixel **palette,389uint32_t *paletteLength,390uint32_t **quantizedPixels,391int withAlpha392) {393ColorCube fineCube = NULL;394ColorCube coarseCube = NULL;395ColorCube lookupCube = NULL;396ColorCube coarseLookupCube = NULL;397ColorBucket paletteBucketsCoarse = NULL;398ColorBucket paletteBucketsFine = NULL;399ColorBucket paletteBuckets = NULL;400uint32_t *qp = NULL;401long i;402unsigned long nCoarseColors, nFineColors, nAlreadySubtracted;403const unsigned int *cubeBits;404
405if (withAlpha) {406cubeBits = CUBE_LEVELS_ALPHA;407} else {408cubeBits = CUBE_LEVELS;409}410
411/*412Create two color cubes, one fine grained with 8x16x8=1024
413colors buckets and a coarse with 4x4x4=64 color buckets.
414The coarse one guarantees that there are color buckets available for
415the whole color range (assuming nQuantPixels > 64).
416
417For a quantization to 256 colors all 64 coarse colors will be used
418plus the 192 most used color buckets from the fine color cube.
419The average of all colors within one bucket is used as the actual
420color for that bucket.
421
422For images with alpha the cubes gets a forth dimension,
4238x16x8x8 and 4x4x4x4.
424*/
425
426/* create fine cube */427fineCube = new_color_cube(cubeBits[0], cubeBits[1], cubeBits[2], cubeBits[3]);428if (!fineCube) {429goto error;430}431for (i = 0; i < nPixels; i++) {432add_color_to_color_cube(fineCube, &pixelData[i]);433}434
435/* create coarse cube */436coarseCube =437copy_color_cube(fineCube, cubeBits[4], cubeBits[5], cubeBits[6], cubeBits[7]);438if (!coarseCube) {439goto error;440}441nCoarseColors = count_used_color_buckets(coarseCube);442
443/* limit to nQuantPixels */444if (nCoarseColors > nQuantPixels) {445nCoarseColors = nQuantPixels;446}447
448/* how many space do we have in our palette for fine colors? */449nFineColors = nQuantPixels - nCoarseColors;450
451/* create fine color palette */452paletteBucketsFine = create_sorted_color_palette(fineCube);453if (!paletteBucketsFine) {454goto error;455}456
457/* remove the used fine colors from the coarse cube */458subtract_color_buckets(coarseCube, paletteBucketsFine, nFineColors);459
460/* did the subtraction cleared one or more coarse bucket? */461while (nCoarseColors > count_used_color_buckets(coarseCube)) {462/* then we can use the free buckets for fine colors */463nAlreadySubtracted = nFineColors;464nCoarseColors = count_used_color_buckets(coarseCube);465nFineColors = nQuantPixels - nCoarseColors;466subtract_color_buckets(467coarseCube,468&paletteBucketsFine[nAlreadySubtracted],469nFineColors - nAlreadySubtracted470);471}472
473/* create our palette buckets with fine and coarse combined */474paletteBucketsCoarse = create_sorted_color_palette(coarseCube);475if (!paletteBucketsCoarse) {476goto error;477}478paletteBuckets = combined_palette(479paletteBucketsCoarse, nCoarseColors, paletteBucketsFine, nFineColors480);481
482free(paletteBucketsFine);483paletteBucketsFine = NULL;484free(paletteBucketsCoarse);485paletteBucketsCoarse = NULL;486if (!paletteBuckets) {487goto error;488}489
490/* add all coarse colors to our coarse lookup cube. */491coarseLookupCube =492new_color_cube(cubeBits[4], cubeBits[5], cubeBits[6], cubeBits[7]);493if (!coarseLookupCube) {494goto error;495}496add_lookup_buckets(coarseLookupCube, paletteBuckets, nCoarseColors, 0);497
498/* expand coarse cube (64) to larger fine cube (4k). the value of each499coarse bucket is then present in the according 64 fine buckets. */
500lookupCube = copy_color_cube(501coarseLookupCube, cubeBits[0], cubeBits[1], cubeBits[2], cubeBits[3]502);503if (!lookupCube) {504goto error;505}506
507/* add fine colors to the lookup cube */508add_lookup_buckets(lookupCube, paletteBuckets, nFineColors, nCoarseColors);509
510/* create result pixels and map palette indices */511/* malloc check ok, calloc for overflow */512qp = calloc(nPixels, sizeof(Pixel));513if (!qp) {514goto error;515}516map_image_pixels(pixelData, nPixels, lookupCube, qp);517
518/* convert palette buckets to RGB pixel palette */519*palette = create_palette_array(paletteBuckets, nQuantPixels);520if (!(*palette)) {521goto error;522}523
524*quantizedPixels = qp;525*paletteLength = nQuantPixels;526
527free_color_cube(coarseCube);528free_color_cube(fineCube);529free_color_cube(lookupCube);530free_color_cube(coarseLookupCube);531free(paletteBuckets);532return 1;533
534error:535/* everything is initialized to NULL536so we are safe to call free */
537free(qp);538free_color_cube(lookupCube);539free_color_cube(coarseLookupCube);540free(paletteBuckets);541free(paletteBucketsCoarse);542free(paletteBucketsFine);543free_color_cube(coarseCube);544free_color_cube(fineCube);545return 0;546}
547