2013-08-09 00:01:20 +00:00
|
|
|
/* Aseprite
|
2013-01-27 15:13:13 +00:00
|
|
|
* Copyright (C) 2001-2013 David Capello
|
2007-09-18 23:57:02 +00:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program 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 General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*/
|
|
|
|
|
2013-08-06 00:20:19 +00:00
|
|
|
#ifdef HAVE_CONFIG_H
|
2007-09-18 23:57:02 +00:00
|
|
|
#include "config.h"
|
2013-08-06 00:20:19 +00:00
|
|
|
#endif
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2013-11-09 22:59:05 +00:00
|
|
|
#include "raster/quantization.h"
|
2011-01-14 21:22:23 +00:00
|
|
|
|
2010-12-26 13:57:03 +00:00
|
|
|
#include "gfx/hsv.h"
|
|
|
|
#include "gfx/rgb.h"
|
|
|
|
#include "raster/blend.h"
|
2011-01-14 21:22:23 +00:00
|
|
|
#include "raster/color_histogram.h"
|
2007-09-18 23:57:02 +00:00
|
|
|
#include "raster/image.h"
|
2013-11-09 22:59:05 +00:00
|
|
|
#include "raster/image_bits.h"
|
2010-12-26 20:03:35 +00:00
|
|
|
#include "raster/images_collector.h"
|
2013-03-11 23:29:45 +00:00
|
|
|
#include "raster/layer.h"
|
2008-03-22 18:43:56 +00:00
|
|
|
#include "raster/palette.h"
|
2013-11-09 22:59:05 +00:00
|
|
|
#include "raster/primitives.h"
|
2010-12-26 13:57:03 +00:00
|
|
|
#include "raster/rgbmap.h"
|
2010-12-26 20:03:35 +00:00
|
|
|
#include "raster/sprite.h"
|
2008-03-22 18:43:56 +00:00
|
|
|
|
2013-11-09 22:59:05 +00:00
|
|
|
#include <algorithm>
|
|
|
|
#include <limits>
|
|
|
|
#include <vector>
|
|
|
|
|
2013-08-06 00:20:19 +00:00
|
|
|
namespace raster {
|
|
|
|
namespace quantization {
|
|
|
|
|
2010-12-26 13:57:03 +00:00
|
|
|
using namespace gfx;
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2010-12-26 13:57:03 +00:00
|
|
|
// Converts a RGB image to indexed with ordered dithering method.
|
2014-07-20 01:01:39 +00:00
|
|
|
static Image* ordered_dithering(
|
|
|
|
const Image* src_image,
|
|
|
|
Image* dst_image,
|
|
|
|
int offsetx, int offsety,
|
|
|
|
const RgbMap* rgbmap,
|
|
|
|
const Palette* palette);
|
|
|
|
|
|
|
|
Palette* create_palette_from_rgb(
|
|
|
|
const Sprite* sprite,
|
|
|
|
FrameNumber frameNumber,
|
|
|
|
Palette* palette)
|
2007-09-18 23:57:02 +00:00
|
|
|
{
|
2014-07-20 01:01:39 +00:00
|
|
|
if (!palette)
|
|
|
|
palette = new Palette(FrameNumber(0), 256);
|
|
|
|
|
2014-07-30 04:28:15 +00:00
|
|
|
bool has_background_layer = (sprite->backgroundLayer() != NULL);
|
2008-09-30 21:01:54 +00:00
|
|
|
Image* flat_image;
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2014-07-30 04:28:15 +00:00
|
|
|
ImagesCollector images(sprite->folder(), // All layers
|
2013-03-11 23:29:45 +00:00
|
|
|
frameNumber, // Ignored, we'll use all frames
|
|
|
|
true, // All frames,
|
2012-01-05 22:45:03 +00:00
|
|
|
false); // forWrite=false, read only
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2010-12-26 20:03:35 +00:00
|
|
|
// Add a flat image with the current sprite's frame rendered
|
2014-07-30 04:28:15 +00:00
|
|
|
flat_image = Image::create(sprite->pixelFormat(), sprite->width(), sprite->height());
|
2013-11-09 22:59:05 +00:00
|
|
|
clear_image(flat_image, 0);
|
2013-03-11 23:29:45 +00:00
|
|
|
sprite->render(flat_image, 0, 0, frameNumber);
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2010-12-26 20:03:35 +00:00
|
|
|
// Create an array of images
|
|
|
|
size_t nimage = images.size() + 1; // +1 for flat_image
|
|
|
|
std::vector<Image*> image_array(nimage);
|
2008-03-22 18:43:56 +00:00
|
|
|
|
2010-12-26 20:03:35 +00:00
|
|
|
size_t c = 0;
|
|
|
|
for (ImagesCollector::ItemsIterator it=images.begin(); it!=images.end(); ++it)
|
|
|
|
image_array[c++] = it->image();
|
|
|
|
image_array[c++] = flat_image; // The 'flat_image'
|
2012-01-05 22:45:03 +00:00
|
|
|
|
2010-12-26 20:03:35 +00:00
|
|
|
// Generate an optimized palette for all images
|
2014-07-20 01:01:39 +00:00
|
|
|
create_palette_from_images(image_array, palette, has_background_layer);
|
2010-12-26 13:57:03 +00:00
|
|
|
|
2010-12-26 20:03:35 +00:00
|
|
|
delete flat_image;
|
2010-12-26 13:57:03 +00:00
|
|
|
return palette;
|
|
|
|
}
|
|
|
|
|
2014-07-20 01:01:39 +00:00
|
|
|
Image* convert_pixel_format(
|
|
|
|
const Image* image,
|
|
|
|
Image* new_image,
|
|
|
|
PixelFormat pixelFormat,
|
|
|
|
DitheringMethod ditheringMethod,
|
|
|
|
const RgbMap* rgbmap,
|
|
|
|
const Palette* palette,
|
|
|
|
bool is_background)
|
2010-12-26 13:57:03 +00:00
|
|
|
{
|
2014-07-20 01:01:39 +00:00
|
|
|
if (!new_image)
|
2014-07-30 04:28:15 +00:00
|
|
|
new_image = Image::create(pixelFormat, image->width(), image->height());
|
2014-07-20 01:01:39 +00:00
|
|
|
|
2010-12-26 13:57:03 +00:00
|
|
|
// RGB -> Indexed with ordered dithering
|
2014-07-30 04:28:15 +00:00
|
|
|
if (image->pixelFormat() == IMAGE_RGB &&
|
2014-03-30 00:31:27 +00:00
|
|
|
pixelFormat == IMAGE_INDEXED &&
|
|
|
|
ditheringMethod == DITHERING_ORDERED) {
|
2014-07-20 01:01:39 +00:00
|
|
|
return ordered_dithering(image, new_image, 0, 0, rgbmap, palette);
|
2010-12-26 13:57:03 +00:00
|
|
|
}
|
|
|
|
|
2013-11-09 22:59:05 +00:00
|
|
|
color_t c;
|
|
|
|
int r, g, b;
|
2010-12-26 13:57:03 +00:00
|
|
|
|
2014-07-30 04:28:15 +00:00
|
|
|
switch (image->pixelFormat()) {
|
2010-12-26 13:57:03 +00:00
|
|
|
|
2013-11-09 22:59:05 +00:00
|
|
|
case IMAGE_RGB: {
|
|
|
|
const LockImageBits<RgbTraits> srcBits(image);
|
|
|
|
LockImageBits<RgbTraits>::const_iterator src_it = srcBits.begin(), src_end = srcBits.end();
|
2010-12-26 13:57:03 +00:00
|
|
|
|
2014-07-30 04:28:15 +00:00
|
|
|
switch (new_image->pixelFormat()) {
|
2011-01-14 21:29:25 +00:00
|
|
|
|
2014-03-30 00:31:27 +00:00
|
|
|
// RGB -> RGB
|
|
|
|
case IMAGE_RGB:
|
|
|
|
new_image->copy(image, 0, 0);
|
|
|
|
break;
|
|
|
|
|
2012-01-05 22:45:03 +00:00
|
|
|
// RGB -> Grayscale
|
2013-11-09 22:59:05 +00:00
|
|
|
case IMAGE_GRAYSCALE: {
|
|
|
|
LockImageBits<GrayscaleTraits> dstBits(new_image, Image::WriteLock);
|
|
|
|
LockImageBits<GrayscaleTraits>::iterator dst_it = dstBits.begin(), dst_end = dstBits.end();
|
|
|
|
|
|
|
|
for (; src_it != src_end; ++src_it, ++dst_it) {
|
|
|
|
ASSERT(dst_it != dst_end);
|
|
|
|
c = *src_it;
|
|
|
|
|
|
|
|
g = 255 * Hsv(Rgb(rgba_getr(c),
|
|
|
|
rgba_getg(c),
|
|
|
|
rgba_getb(c))).valueInt() / 100;
|
|
|
|
|
|
|
|
*dst_it = graya(g, rgba_geta(c));
|
2012-01-05 22:45:03 +00:00
|
|
|
}
|
2013-11-09 22:59:05 +00:00
|
|
|
ASSERT(dst_it == dst_end);
|
2012-01-05 22:45:03 +00:00
|
|
|
break;
|
2013-11-09 22:59:05 +00:00
|
|
|
}
|
2012-01-05 22:45:03 +00:00
|
|
|
|
|
|
|
// RGB -> Indexed
|
2013-11-09 22:59:05 +00:00
|
|
|
case IMAGE_INDEXED: {
|
|
|
|
LockImageBits<IndexedTraits> dstBits(new_image, Image::WriteLock);
|
|
|
|
LockImageBits<IndexedTraits>::iterator dst_it = dstBits.begin(), dst_end = dstBits.end();
|
|
|
|
|
|
|
|
for (; src_it != src_end; ++src_it, ++dst_it) {
|
|
|
|
ASSERT(dst_it != dst_end);
|
|
|
|
c = *src_it;
|
|
|
|
|
|
|
|
r = rgba_getr(c);
|
|
|
|
g = rgba_getg(c);
|
|
|
|
b = rgba_getb(c);
|
2014-03-30 00:31:27 +00:00
|
|
|
|
2013-11-09 22:59:05 +00:00
|
|
|
if (rgba_geta(c) == 0)
|
|
|
|
*dst_it = 0;
|
2012-01-05 22:45:03 +00:00
|
|
|
else
|
2013-11-09 22:59:05 +00:00
|
|
|
*dst_it = rgbmap->mapColor(r, g, b);
|
2012-01-05 22:45:03 +00:00
|
|
|
}
|
2013-11-09 22:59:05 +00:00
|
|
|
ASSERT(dst_it == dst_end);
|
2012-01-05 22:45:03 +00:00
|
|
|
break;
|
2013-11-09 22:59:05 +00:00
|
|
|
}
|
2010-12-26 13:57:03 +00:00
|
|
|
}
|
|
|
|
break;
|
2013-11-09 22:59:05 +00:00
|
|
|
}
|
2010-12-26 13:57:03 +00:00
|
|
|
|
2013-11-09 22:59:05 +00:00
|
|
|
case IMAGE_GRAYSCALE: {
|
|
|
|
const LockImageBits<GrayscaleTraits> srcBits(image);
|
|
|
|
LockImageBits<GrayscaleTraits>::const_iterator src_it = srcBits.begin(), src_end = srcBits.end();
|
2010-12-26 13:57:03 +00:00
|
|
|
|
2014-07-30 04:28:15 +00:00
|
|
|
switch (new_image->pixelFormat()) {
|
2011-01-14 21:29:25 +00:00
|
|
|
|
2012-01-05 22:45:03 +00:00
|
|
|
// Grayscale -> RGB
|
2013-11-09 22:59:05 +00:00
|
|
|
case IMAGE_RGB: {
|
|
|
|
LockImageBits<RgbTraits> dstBits(new_image, Image::WriteLock);
|
|
|
|
LockImageBits<RgbTraits>::iterator dst_it = dstBits.begin(), dst_end = dstBits.end();
|
|
|
|
|
|
|
|
for (; src_it != src_end; ++src_it, ++dst_it) {
|
|
|
|
ASSERT(dst_it != dst_end);
|
|
|
|
c = *src_it;
|
|
|
|
|
|
|
|
g = graya_getv(c);
|
|
|
|
|
|
|
|
*dst_it = rgba(g, g, g, graya_geta(c));
|
2012-01-05 22:45:03 +00:00
|
|
|
}
|
2013-11-09 22:59:05 +00:00
|
|
|
ASSERT(dst_it == dst_end);
|
2012-01-05 22:45:03 +00:00
|
|
|
break;
|
2013-11-09 22:59:05 +00:00
|
|
|
}
|
2012-01-05 22:45:03 +00:00
|
|
|
|
2014-03-30 00:31:27 +00:00
|
|
|
// Grayscale -> Grayscale
|
|
|
|
case IMAGE_GRAYSCALE:
|
|
|
|
new_image->copy(image, 0, 0);
|
|
|
|
break;
|
|
|
|
|
2012-01-05 22:45:03 +00:00
|
|
|
// Grayscale -> Indexed
|
2013-11-09 22:59:05 +00:00
|
|
|
case IMAGE_INDEXED: {
|
|
|
|
LockImageBits<IndexedTraits> dstBits(new_image, Image::WriteLock);
|
|
|
|
LockImageBits<IndexedTraits>::iterator dst_it = dstBits.begin(), dst_end = dstBits.end();
|
|
|
|
|
|
|
|
for (; src_it != src_end; ++src_it, ++dst_it) {
|
|
|
|
ASSERT(dst_it != dst_end);
|
|
|
|
c = *src_it;
|
|
|
|
|
|
|
|
if (graya_geta(c) == 0)
|
|
|
|
*dst_it = 0;
|
2012-01-05 22:45:03 +00:00
|
|
|
else
|
2013-11-09 22:59:05 +00:00
|
|
|
*dst_it = graya_getv(c);
|
2012-01-05 22:45:03 +00:00
|
|
|
}
|
2013-11-09 22:59:05 +00:00
|
|
|
ASSERT(dst_it == dst_end);
|
2012-01-05 22:45:03 +00:00
|
|
|
break;
|
2013-11-09 22:59:05 +00:00
|
|
|
}
|
2010-12-26 13:57:03 +00:00
|
|
|
}
|
|
|
|
break;
|
2013-11-09 22:59:05 +00:00
|
|
|
}
|
2010-12-26 13:57:03 +00:00
|
|
|
|
2013-11-09 22:59:05 +00:00
|
|
|
case IMAGE_INDEXED: {
|
|
|
|
const LockImageBits<IndexedTraits> srcBits(image);
|
|
|
|
LockImageBits<IndexedTraits>::const_iterator src_it = srcBits.begin(), src_end = srcBits.end();
|
2010-12-26 13:57:03 +00:00
|
|
|
|
2014-07-30 04:28:15 +00:00
|
|
|
switch (new_image->pixelFormat()) {
|
2011-01-14 21:29:25 +00:00
|
|
|
|
2012-01-05 22:45:03 +00:00
|
|
|
// Indexed -> RGB
|
2013-11-09 22:59:05 +00:00
|
|
|
case IMAGE_RGB: {
|
|
|
|
LockImageBits<RgbTraits> dstBits(new_image, Image::WriteLock);
|
|
|
|
LockImageBits<RgbTraits>::iterator dst_it = dstBits.begin(), dst_end = dstBits.end();
|
|
|
|
|
|
|
|
for (; src_it != src_end; ++src_it, ++dst_it) {
|
|
|
|
ASSERT(dst_it != dst_end);
|
|
|
|
c = *src_it;
|
2012-01-05 22:45:03 +00:00
|
|
|
|
2014-07-30 04:28:15 +00:00
|
|
|
if (!is_background && c == image->maskColor())
|
2013-11-09 22:59:05 +00:00
|
|
|
*dst_it = 0;
|
2012-01-05 22:45:03 +00:00
|
|
|
else
|
2013-11-09 22:59:05 +00:00
|
|
|
*dst_it = rgba(rgba_getr(palette->getEntry(c)),
|
|
|
|
rgba_getg(palette->getEntry(c)),
|
|
|
|
rgba_getb(palette->getEntry(c)), 255);
|
2012-01-05 22:45:03 +00:00
|
|
|
}
|
2013-11-09 22:59:05 +00:00
|
|
|
ASSERT(dst_it == dst_end);
|
2012-01-05 22:45:03 +00:00
|
|
|
break;
|
2013-11-09 22:59:05 +00:00
|
|
|
}
|
2012-01-05 22:45:03 +00:00
|
|
|
|
|
|
|
// Indexed -> Grayscale
|
2013-11-09 22:59:05 +00:00
|
|
|
case IMAGE_GRAYSCALE: {
|
|
|
|
LockImageBits<GrayscaleTraits> dstBits(new_image, Image::WriteLock);
|
|
|
|
LockImageBits<GrayscaleTraits>::iterator dst_it = dstBits.begin(), dst_end = dstBits.end();
|
|
|
|
|
|
|
|
for (; src_it != src_end; ++src_it, ++dst_it) {
|
|
|
|
ASSERT(dst_it != dst_end);
|
|
|
|
c = *src_it;
|
2012-01-05 22:45:03 +00:00
|
|
|
|
2014-07-30 04:28:15 +00:00
|
|
|
if (!is_background && c == image->maskColor())
|
2013-11-09 22:59:05 +00:00
|
|
|
*dst_it = 0;
|
2012-01-05 22:45:03 +00:00
|
|
|
else {
|
2013-11-09 22:59:05 +00:00
|
|
|
r = rgba_getr(palette->getEntry(c));
|
|
|
|
g = rgba_getg(palette->getEntry(c));
|
|
|
|
b = rgba_getb(palette->getEntry(c));
|
2012-01-05 22:45:03 +00:00
|
|
|
|
|
|
|
g = 255 * Hsv(Rgb(r, g, b)).valueInt() / 100;
|
2013-11-09 22:59:05 +00:00
|
|
|
*dst_it = graya(g, 255);
|
2012-01-05 22:45:03 +00:00
|
|
|
}
|
|
|
|
}
|
2013-11-09 22:59:05 +00:00
|
|
|
ASSERT(dst_it == dst_end);
|
2012-01-05 22:45:03 +00:00
|
|
|
break;
|
2013-11-09 22:59:05 +00:00
|
|
|
}
|
2011-01-14 21:29:25 +00:00
|
|
|
|
2014-03-30 00:31:27 +00:00
|
|
|
// Indexed -> Indexed
|
|
|
|
case IMAGE_INDEXED: {
|
|
|
|
LockImageBits<IndexedTraits> dstBits(new_image, Image::WriteLock);
|
|
|
|
LockImageBits<IndexedTraits>::iterator dst_it = dstBits.begin(), dst_end = dstBits.end();
|
2014-07-30 04:28:15 +00:00
|
|
|
color_t dstMaskColor = new_image->maskColor();
|
2014-03-30 00:31:27 +00:00
|
|
|
|
|
|
|
for (; src_it != src_end; ++src_it, ++dst_it) {
|
|
|
|
ASSERT(dst_it != dst_end);
|
|
|
|
c = *src_it;
|
|
|
|
|
2014-07-30 04:28:15 +00:00
|
|
|
if (!is_background && c == image->maskColor())
|
2014-03-30 00:31:27 +00:00
|
|
|
*dst_it = dstMaskColor;
|
|
|
|
else {
|
|
|
|
r = rgba_getr(palette->getEntry(c));
|
|
|
|
g = rgba_getg(palette->getEntry(c));
|
|
|
|
b = rgba_getb(palette->getEntry(c));
|
|
|
|
|
|
|
|
*dst_it = rgbmap->mapColor(r, g, b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ASSERT(dst_it == dst_end);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2010-12-26 13:57:03 +00:00
|
|
|
}
|
|
|
|
break;
|
2013-11-09 22:59:05 +00:00
|
|
|
}
|
2010-12-26 13:57:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return new_image;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Based on Gary Oberbrunner: */
|
|
|
|
/*----------------------------------------------------------------------
|
|
|
|
* Color image quantizer, from Paul Heckbert's paper in
|
|
|
|
* Computer Graphics, vol.16 #3, July 1982 (Siggraph proceedings),
|
|
|
|
* pp. 297-304.
|
|
|
|
* By Gary Oberbrunner, copyright c. 1988.
|
|
|
|
*----------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
2012-01-05 22:45:03 +00:00
|
|
|
/* Bayer-method ordered dither. The array line[] contains the
|
2010-12-26 13:57:03 +00:00
|
|
|
* intensity values for the line being processed. As you can see, the
|
|
|
|
* ordered dither is much simpler than the error dispersion dither.
|
|
|
|
* It is also many times faster, but it is not as accurate and
|
|
|
|
* produces cross-hatch * patterns on the output.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int pattern[8][8] = {
|
|
|
|
{ 0, 32, 8, 40, 2, 34, 10, 42 }, /* 8x8 Bayer ordered dithering */
|
|
|
|
{ 48, 16, 56, 24, 50, 18, 58, 26 }, /* pattern. Each input pixel */
|
|
|
|
{ 12, 44, 4, 36, 14, 46, 6, 38 }, /* is scaled to the 0..63 range */
|
|
|
|
{ 60, 28, 52, 20, 62, 30, 54, 22 }, /* before looking in this table */
|
|
|
|
{ 3, 35, 11, 43, 1, 33, 9, 41 }, /* to determine the action. */
|
|
|
|
{ 51, 19, 59, 27, 49, 17, 57, 25 },
|
|
|
|
{ 15, 47, 7, 39, 13, 45, 5, 37 },
|
|
|
|
{ 63, 31, 55, 23, 61, 29, 53, 21 }
|
|
|
|
};
|
|
|
|
|
2012-01-05 22:45:03 +00:00
|
|
|
#define DIST(r1,g1,b1,r2,g2,b2) (3 * ((r1)-(r2)) * ((r1)-(r2)) + \
|
|
|
|
4 * ((g1)-(g2)) * ((g1)-(g2)) + \
|
|
|
|
2 * ((b1)-(b2)) * ((b1)-(b2)))
|
2010-12-26 13:57:03 +00:00
|
|
|
|
2014-07-20 01:01:39 +00:00
|
|
|
static Image* ordered_dithering(
|
|
|
|
const Image* src_image,
|
|
|
|
Image* dst_image,
|
|
|
|
int offsetx, int offsety,
|
|
|
|
const RgbMap* rgbmap,
|
|
|
|
const Palette* palette)
|
2010-12-26 13:57:03 +00:00
|
|
|
{
|
|
|
|
int oppr, oppg, oppb, oppnrcm;
|
|
|
|
int dither_const;
|
|
|
|
int nr, ng, nb;
|
|
|
|
int r, g, b, a;
|
|
|
|
int nearestcm;
|
2013-11-09 22:59:05 +00:00
|
|
|
int x, y;
|
|
|
|
color_t c;
|
2010-12-26 13:57:03 +00:00
|
|
|
|
2013-11-09 22:59:05 +00:00
|
|
|
const LockImageBits<RgbTraits> src_bits(src_image);
|
|
|
|
LockImageBits<IndexedTraits> dst_bits(dst_image);
|
|
|
|
LockImageBits<RgbTraits>::const_iterator src_it = src_bits.begin();
|
|
|
|
LockImageBits<IndexedTraits>::iterator dst_it = dst_bits.begin();
|
|
|
|
|
2014-07-30 04:28:15 +00:00
|
|
|
for (y=0; y<src_image->height(); ++y) {
|
|
|
|
for (x=0; x<src_image->width(); ++x, ++src_it, ++dst_it) {
|
2013-11-09 22:59:05 +00:00
|
|
|
ASSERT(src_it != src_bits.end());
|
|
|
|
ASSERT(dst_it != dst_bits.end());
|
2010-12-26 13:57:03 +00:00
|
|
|
|
2013-11-09 22:59:05 +00:00
|
|
|
c = *src_it;
|
|
|
|
|
|
|
|
r = rgba_getr(c);
|
|
|
|
g = rgba_getg(c);
|
|
|
|
b = rgba_getb(c);
|
|
|
|
a = rgba_geta(c);
|
2010-12-26 13:57:03 +00:00
|
|
|
|
|
|
|
if (a != 0) {
|
2012-01-05 22:45:03 +00:00
|
|
|
nearestcm = rgbmap->mapColor(r, g, b);
|
|
|
|
/* rgb values for nearest color */
|
2013-11-09 22:59:05 +00:00
|
|
|
nr = rgba_getr(palette->getEntry(nearestcm));
|
|
|
|
ng = rgba_getg(palette->getEntry(nearestcm));
|
|
|
|
nb = rgba_getb(palette->getEntry(nearestcm));
|
2012-01-05 22:45:03 +00:00
|
|
|
/* Color as far from rgb as nrngnb but in the other direction */
|
|
|
|
oppr = MID(0, 2*r - nr, 255);
|
|
|
|
oppg = MID(0, 2*g - ng, 255);
|
|
|
|
oppb = MID(0, 2*b - nb, 255);
|
|
|
|
/* Nearest match for opposite color: */
|
|
|
|
oppnrcm = rgbmap->mapColor(oppr, oppg, oppb);
|
|
|
|
/* If they're not the same, dither between them. */
|
|
|
|
/* Dither constant is measured by where the true
|
|
|
|
color lies between the two nearest approximations.
|
|
|
|
Since the most nearly opposite color is not necessarily
|
|
|
|
on the line from the nearest through the true color,
|
|
|
|
some triangulation error can be introduced. In the worst
|
|
|
|
case the r-nr distance can actually be less than the nr-oppr
|
|
|
|
distance. */
|
|
|
|
if (oppnrcm != nearestcm) {
|
2013-11-09 22:59:05 +00:00
|
|
|
oppr = rgba_getr(palette->getEntry(oppnrcm));
|
|
|
|
oppg = rgba_getg(palette->getEntry(oppnrcm));
|
|
|
|
oppb = rgba_getb(palette->getEntry(oppnrcm));
|
2012-01-05 22:45:03 +00:00
|
|
|
|
|
|
|
dither_const = DIST(nr, ng, nb, oppr, oppg, oppb);
|
|
|
|
if (dither_const != 0) {
|
|
|
|
dither_const = 64 * DIST(r, g, b, nr, ng, nb) / dither_const;
|
|
|
|
dither_const = MIN(63, dither_const);
|
|
|
|
|
|
|
|
if (pattern[(x+offsetx) & 7][(y+offsety) & 7] < dither_const)
|
|
|
|
nearestcm = oppnrcm;
|
|
|
|
}
|
|
|
|
}
|
2010-12-26 13:57:03 +00:00
|
|
|
}
|
|
|
|
else
|
2012-01-05 22:45:03 +00:00
|
|
|
nearestcm = 0;
|
2010-12-26 13:57:03 +00:00
|
|
|
|
2013-11-09 22:59:05 +00:00
|
|
|
*dst_it = nearestcm;
|
2010-12-26 13:57:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return dst_image;
|
2007-09-18 23:57:02 +00:00
|
|
|
}
|
|
|
|
|
2011-01-14 21:22:23 +00:00
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
// Creation of optimized palette for RGB images
|
|
|
|
// by David Capello
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2014-07-20 01:01:39 +00:00
|
|
|
void create_palette_from_images(const std::vector<Image*>& images, Palette* palette, bool has_background_layer)
|
2007-09-18 23:57:02 +00:00
|
|
|
{
|
2011-01-14 21:22:23 +00:00
|
|
|
quantization::ColorHistogram<5, 6, 5> histogram;
|
2011-03-24 21:36:19 +00:00
|
|
|
uint32_t color;
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2011-01-14 21:22:23 +00:00
|
|
|
// If the sprite has a background layer, the first entry can be
|
|
|
|
// used, in other case the 0 indexed will be the mask color, so it
|
|
|
|
// will not be used later in the color conversion (from RGB to
|
|
|
|
// Indexed).
|
|
|
|
int first_usable_entry = (has_background_layer ? 0: 1);
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2011-01-14 21:22:23 +00:00
|
|
|
for (int i=0; i<(int)images.size(); ++i) {
|
|
|
|
const Image* image = images[i];
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2014-07-30 04:28:15 +00:00
|
|
|
switch (image->pixelFormat()) {
|
2014-07-20 01:01:39 +00:00
|
|
|
|
|
|
|
case IMAGE_RGB:
|
|
|
|
{
|
|
|
|
const LockImageBits<RgbTraits> bits(image);
|
|
|
|
LockImageBits<RgbTraits>::const_iterator it = bits.begin(), end = bits.end();
|
|
|
|
|
|
|
|
for (; it != end; ++it) {
|
|
|
|
color = *it;
|
|
|
|
|
|
|
|
if (rgba_geta(color) > 0) {
|
|
|
|
color |= rgba(0, 0, 0, 255);
|
|
|
|
histogram.addSamples(color, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IMAGE_GRAYSCALE:
|
|
|
|
{
|
|
|
|
const LockImageBits<RgbTraits> bits(image);
|
|
|
|
LockImageBits<RgbTraits>::const_iterator it = bits.begin(), end = bits.end();
|
|
|
|
|
|
|
|
for (; it != end; ++it) {
|
|
|
|
color = *it;
|
|
|
|
|
|
|
|
if (graya_geta(color) > 0) {
|
|
|
|
color = graya_getv(color);
|
|
|
|
histogram.addSamples(rgba(color, color, color, 255), 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case IMAGE_INDEXED:
|
|
|
|
ASSERT(false);
|
|
|
|
break;
|
2007-09-18 23:57:02 +00:00
|
|
|
|
2008-04-14 03:05:19 +00:00
|
|
|
}
|
2007-09-18 23:57:02 +00:00
|
|
|
}
|
|
|
|
|
2011-01-14 21:22:23 +00:00
|
|
|
int used_colors = histogram.createOptimizedPalette(palette, first_usable_entry, 255);
|
|
|
|
//palette->resize(first_usable_entry+used_colors); // TODO
|
2007-09-18 23:57:02 +00:00
|
|
|
}
|
2013-08-06 00:20:19 +00:00
|
|
|
|
|
|
|
} // namespace quantization
|
|
|
|
} // namespace raster
|