* Halftone.cpp
* Copyright 1999-2000 Y.Takagi. All Rights Reserved.
*/
#include <Debug.h>
#include <InterfaceDefs.h>
#include <math.h>
#include <memory>
#include <string.h>
#include "Halftone.h"
#include "ValidRect.h"
#include "DbgMsg.h"
using namespace std;
#include "Pattern.h"
static uint
ToGray(ColorRGB32 c)
{
if (c.little.red == c.little.green && c.little.red == c.little.blue)
return c.little.red;
return (c.little.red * 3 + c.little.green * 6 + c.little.blue) / 10;
}
static uint
GetRedValue(ColorRGB32 c)
{
return c.little.red;
}
static uint
GetGreenValue(ColorRGB32 c)
{
return c.little.green;
}
static uint
GetBlueValue(ColorRGB32 c)
{
return c.little.blue;
}
Halftone::Halftone(color_space colorSpace, double gamma, double min,
DitherType ditherType)
{
fPixelDepth = color_space2pixel_depth(colorSpace);
fGray = ToGray;
SetPlanes(kPlaneMonochrome1);
SetBlackValue(kHighValueMeansBlack);
InitFloydSteinberg();
CreateGammaTable(gamma, min);
if (ditherType == kTypeFloydSteinberg) {
fDither = &Halftone::DitherFloydSteinberg;
return;
}
switch (ditherType) {
case kType2:
fPattern = pattern16x16_type2;
break;
case kType3:
fPattern = pattern16x16_type3;
break;
default:
fPattern = pattern16x16_type1;
break;
}
switch (colorSpace) {
case B_RGB32:
case B_RGB32_BIG:
fDither = &Halftone::DitherRGB32;
break;
default:
fDither = NULL;
break;
}
}
Halftone::~Halftone()
{
UninitFloydSteinberg();
}
void
Halftone::SetPlanes(Planes planes)
{
fPlanes = planes;
if (planes == kPlaneMonochrome1) {
fNumberOfPlanes = 1;
fGray = ToGray;
} else {
ASSERT(planes == kPlaneRGB1);
fNumberOfPlanes = 3;
}
fCurrentPlane = 0;
}
void
Halftone::SetBlackValue(BlackValue blackValue)
{
fBlackValue = blackValue;
}
void
Halftone::CreateGammaTable(double gamma, double min)
{
const double kScalingFactor = 255.0 - min;
for (int i = 0; i < kGammaTableSize; i++) {
const double kGammaCorrectedValue = pow((double)i / 255.0, gamma);
const double kTranslatedValue = min + kGammaCorrectedValue * kScalingFactor;
fGammaTable[i] = (uint)(kTranslatedValue);
}
}
void
Halftone::InitElements(int x, int y, uchar* elements)
{
x &= 0x0F;
y &= 0x0F;
const uchar *left = &fPattern[y * 16];
const uchar *pos = left + x;
const uchar *right = left + 0x0F;
for (int i = 0; i < 16; i++) {
elements[i] = *pos;
if (pos >= right)
pos = left;
else
pos++;
}
}
void
Halftone::Dither(uchar* destination, const uchar* source, int x, int y,
int width)
{
if (fPlanes == kPlaneRGB1) {
switch (fCurrentPlane) {
case 0:
SetGrayFunction(kRedChannel);
break;
case 1:
SetGrayFunction(kGreenChannel);
break;
case 2:
SetGrayFunction(kBlueChannel);
break;
}
} else {
ASSERT(fGray == &ToGray);
}
(this->*fDither)(destination, source, x, y, width);
fCurrentPlane ++;
if (fCurrentPlane >= fNumberOfPlanes)
fCurrentPlane = 0;
}
void
Halftone::SetGrayFunction(GrayFunction grayFunction)
{
PFN_gray function = NULL;
switch (grayFunction) {
case kMixToGray: function = ToGray;
break;
case kRedChannel: function = GetRedValue;
break;
case kGreenChannel: function = GetGreenValue;
break;
case kBlueChannel: function = GetBlueValue;
break;
};
SetGrayFunction(function);
}
void
Halftone::DitherRGB32(uchar *destination, const uchar *source0, int x, int y,
int width)
{
uchar elements[16];
InitElements(x, y, elements);
const ColorRGB32* source = reinterpret_cast<const ColorRGB32*>(source0);
int widthByte = (width + 7) / 8;
int remainder = width % 8;
if (remainder == 0)
remainder = 8;
ColorRGB32 c;
uchar cur;
uint density;
int i, j;
uchar *e = elements;
uchar *last_e = elements + 16;
c = *source;
density = GetDensity(c);
if (width >= 8) {
for (i = 0; i < widthByte - 1; i++) {
cur = 0;
if (e == last_e) {
e = elements;
}
for (j = 0; j < 8; j++) {
if (c.little.red != source->little.red
|| c.little.green != source->little.green
|| c.little.blue != source->little.blue) {
c = *source;
density = GetDensity(c);
}
source++;
if (density <= *e++) {
cur |= (0x80 >> j);
}
}
*destination++ = ConvertUsingBlackValue(cur);
}
}
if (remainder > 0) {
cur = 0;
if (e == last_e) {
e = elements;
}
for (j = 0; j < remainder; j++) {
if (c.little.red != source->little.red
|| c.little.green != source->little.green
|| c.little.blue != source->little.blue) {
c = *source;
density = GetDensity(c);
}
source++;
if (density <= *e++) {
cur |= (0x80 >> j);
}
}
*destination++ = ConvertUsingBlackValue(cur);
}
}
void
Halftone::InitFloydSteinberg()
{
for (int i = 0; i < kMaxNumberOfPlanes; i ++)
fErrorTables[i] = NULL;
}
void
Halftone::DeleteErrorTables()
{
for (int i = 0; i < kMaxNumberOfPlanes; i ++) {
delete fErrorTables[i];
fErrorTables[i] = NULL;
}
}
void
Halftone::UninitFloydSteinberg()
{
DeleteErrorTables();
}
void
Halftone::SetupErrorBuffer(int x, int y, int width)
{
DeleteErrorTables();
fX = x;
fY = y;
fWidth = width;
for (int i = 0; i < fNumberOfPlanes; i ++) {
const int size = width + 2;
fErrorTables[i] = new int[size];
memset(fErrorTables[i], 0, sizeof(int) * size);
}
}
void
Halftone::DitherFloydSteinberg(uchar *destination, const uchar* source0,
int x, int y, int width)
{
if (fErrorTables[fCurrentPlane] == NULL || fX != x
|| (fCurrentPlane == 0 && fY != y - 1)
|| (fCurrentPlane > 0 && fY != y)
|| fWidth != width)
SetupErrorBuffer(x, y, width);
else
fY = y;
int* errorTable = &fErrorTables[fCurrentPlane][1];
int current_error = errorTable[0];
int error;
errorTable[0] = 0;
const ColorRGB32 *source = reinterpret_cast<const ColorRGB32 *>(source0);
uchar cur = 0;
for (int x = 0; x < width; x ++, source ++) {
const int bit = 7 - x % 8;
const int density = GetDensity(*source) + current_error / 16;
if (density < 128) {
error = density;
cur |= (1 << bit);
} else {
error = density - 255;
}
int* right = &errorTable[x+1];
current_error = (*right) + 7 * error;
*right = 1 * error;
int* middle = right - 1;
*middle += 5 * error;
int* left = middle - 1;
*left += 3 * error;
if (bit == 0) {
*destination = ConvertUsingBlackValue(cur);
destination ++;
cur = 0;
}
}
const bool hasRest = (width % 8) != 0;
if (hasRest) {
*destination = ConvertUsingBlackValue(cur);
}
}