1/*
2 * Halftone.cpp
3 * Copyright 1999-2000 Y.Takagi. All Rights Reserved.
4 */
5
6#include <Debug.h>
7#include <InterfaceDefs.h>
8#include <math.h>
9#include <memory>
10#include <string.h>
11#include "Halftone.h"
12#include "ValidRect.h"
13#include "DbgMsg.h"
14
15
16using namespace std;
17
18
19#include "Pattern.h"
20
21
22static uint
23ToGray(ColorRGB32 c)
24{
25	if (c.little.red == c.little.green && c.little.red == c.little.blue)
26		return c.little.red;
27	return (c.little.red * 3 + c.little.green * 6 + c.little.blue) / 10;
28}
29
30
31static uint
32GetRedValue(ColorRGB32 c)
33{
34	return c.little.red;
35}
36
37
38static uint
39GetGreenValue(ColorRGB32 c)
40{
41	return c.little.green;
42}
43
44
45static uint
46GetBlueValue(ColorRGB32 c)
47{
48	return c.little.blue;
49}
50
51
52Halftone::Halftone(color_space colorSpace, double gamma, double min,
53	DitherType ditherType)
54{
55	fPixelDepth = color_space2pixel_depth(colorSpace);
56	fGray       = ToGray;
57	SetPlanes(kPlaneMonochrome1);
58	SetBlackValue(kHighValueMeansBlack);
59
60	InitFloydSteinberg();
61
62	CreateGammaTable(gamma, min);
63
64	if (ditherType == kTypeFloydSteinberg) {
65		fDither = &Halftone::DitherFloydSteinberg;
66		return;
67	}
68
69	switch (ditherType) {
70	case kType2:
71		fPattern = pattern16x16_type2;
72		break;
73	case kType3:
74		fPattern = pattern16x16_type3;
75		break;
76	default:
77		fPattern = pattern16x16_type1;
78		break;
79	}
80
81	switch (colorSpace) {
82	case B_RGB32:
83	case B_RGB32_BIG:
84		fDither = &Halftone::DitherRGB32;
85		break;
86	default:
87		fDither = NULL;
88		break;
89	}
90}
91
92
93Halftone::~Halftone()
94{
95	UninitFloydSteinberg();
96}
97
98
99void
100Halftone::SetPlanes(Planes planes)
101{
102	fPlanes = planes;
103	if (planes == kPlaneMonochrome1) {
104		fNumberOfPlanes = 1;
105		fGray = ToGray;
106	} else {
107		ASSERT(planes == kPlaneRGB1);
108		fNumberOfPlanes = 3;
109	}
110	fCurrentPlane = 0;
111}
112
113
114void
115Halftone::SetBlackValue(BlackValue blackValue)
116{
117	fBlackValue = blackValue;
118}
119
120
121void
122Halftone::CreateGammaTable(double gamma, double min)
123{
124	const double kScalingFactor = 255.0 - min;
125	for (int i = 0; i < kGammaTableSize; i++) {
126		const double kGammaCorrectedValue = pow((double)i / 255.0, gamma);
127		const double kTranslatedValue = min + kGammaCorrectedValue * kScalingFactor;
128		fGammaTable[i] = (uint)(kTranslatedValue);
129	}
130}
131
132
133void
134Halftone::InitElements(int x, int y, uchar* elements)
135{
136	x &= 0x0F;
137	y &= 0x0F;
138
139	const uchar *left  = &fPattern[y * 16];
140	const uchar *pos   = left + x;
141	const uchar *right = left + 0x0F;
142
143	for (int i = 0; i < 16; i++) {
144		elements[i] = *pos;
145		if (pos >= right)
146			pos = left;
147		else
148			pos++;
149	}
150}
151
152
153void
154Halftone::Dither(uchar* destination, const uchar* source, int x, int y,
155	int width)
156{
157	if (fPlanes == kPlaneRGB1) {
158		switch (fCurrentPlane) {
159			case 0:
160				SetGrayFunction(kRedChannel);
161				break;
162			case 1:
163				SetGrayFunction(kGreenChannel);
164				break;
165			case 2:
166				SetGrayFunction(kBlueChannel);
167				break;
168		}
169	} else {
170		ASSERT(fGray == &ToGray);
171	}
172
173	(this->*fDither)(destination, source, x, y, width);
174
175	// next plane
176	fCurrentPlane ++;
177	if (fCurrentPlane >= fNumberOfPlanes)
178		fCurrentPlane = 0;
179}
180
181
182void
183Halftone::SetGrayFunction(GrayFunction grayFunction)
184{
185	PFN_gray function = NULL;
186	switch (grayFunction) {
187		case kMixToGray: function = ToGray;
188			break;
189		case kRedChannel: function = GetRedValue;
190			break;
191		case kGreenChannel: function = GetGreenValue;
192			break;
193		case kBlueChannel: function = GetBlueValue;
194			break;
195	};
196	SetGrayFunction(function);
197}
198
199
200void
201Halftone::DitherRGB32(uchar *destination, const uchar *source0, int x, int y,
202	int width)
203{
204	uchar elements[16];
205	InitElements(x, y, elements);
206
207	const ColorRGB32* source = reinterpret_cast<const ColorRGB32*>(source0);
208
209	int widthByte = (width + 7) / 8;
210	int remainder = width % 8;
211	if (remainder == 0)
212		remainder = 8;
213
214	ColorRGB32 c;
215	uchar cur; // cleared bit means white, set bit means black
216	uint  density;
217	int i, j;
218	uchar *e = elements;
219	uchar *last_e = elements + 16;
220
221	c = *source;
222	density = GetDensity(c);
223
224	if (width >= 8) {
225		for (i = 0; i < widthByte - 1; i++) {
226			cur = 0;
227			if (e == last_e) {
228				e = elements;
229			}
230			for (j = 0; j < 8; j++) {
231				if (c.little.red != source->little.red
232					|| c.little.green != source->little.green
233					|| c.little.blue != source->little.blue) {
234					c = *source;
235					density = GetDensity(c);
236				}
237				source++;
238				if (density <= *e++) {
239					cur |= (0x80 >> j);
240				}
241			}
242			*destination++ = ConvertUsingBlackValue(cur);
243		}
244	}
245
246	if (remainder > 0) {
247		cur = 0;
248		if (e == last_e) {
249			e = elements;
250		}
251		for (j = 0; j < remainder; j++) {
252			if (c.little.red != source->little.red
253				|| c.little.green != source->little.green
254				|| c.little.blue != source->little.blue) {
255				c = *source;
256				density = GetDensity(c);
257			}
258			source++;
259			if (density <= *e++) {
260				cur |= (0x80 >> j);
261			}
262		}
263		*destination++ = ConvertUsingBlackValue(cur);
264	}
265}
266
267
268// Floyd-Steinberg dithering
269void
270Halftone::InitFloydSteinberg()
271{
272	for (int i = 0; i < kMaxNumberOfPlanes; i ++)
273		fErrorTables[i] = NULL;
274}
275
276
277void
278Halftone::DeleteErrorTables()
279{
280	for (int i = 0; i < kMaxNumberOfPlanes; i ++) {
281		delete fErrorTables[i];
282		fErrorTables[i] = NULL;
283	}
284}
285
286
287void
288Halftone::UninitFloydSteinberg()
289{
290	DeleteErrorTables();
291}
292
293
294void
295Halftone::SetupErrorBuffer(int x, int y, int width)
296{
297	DeleteErrorTables();
298	fX = x;
299	fY = y;
300	fWidth = width;
301	for (int i = 0; i < fNumberOfPlanes; i ++) {
302		// reserve also space for sentinals at both ends of error table
303		const int size = width + 2;
304		fErrorTables[i] = new int[size];
305		memset(fErrorTables[i], 0, sizeof(int) * size);
306	}
307}
308
309
310void
311Halftone::DitherFloydSteinberg(uchar *destination, const uchar* source0,
312	int x, int y, int width)
313{
314	if (fErrorTables[fCurrentPlane] == NULL || fX != x
315		|| (fCurrentPlane == 0 && fY != y - 1)
316		|| (fCurrentPlane > 0 && fY != y)
317		|| fWidth != width)
318		SetupErrorBuffer(x, y, width);
319	else
320		fY = y;
321
322	int* errorTable = &fErrorTables[fCurrentPlane][1];
323	int current_error = errorTable[0];
324	int error;
325	errorTable[0] = 0;
326
327	const ColorRGB32 *source = reinterpret_cast<const ColorRGB32 *>(source0);
328	uchar cur = 0; // cleared bit means white, set bit means black
329	for (int x = 0; x < width; x ++, source ++) {
330		const int bit = 7 - x % 8;
331		const int density = GetDensity(*source) + current_error / 16;
332
333		if (density < 128) {
334			error = density;
335			cur |= (1 << bit);
336		} else {
337			error = density - 255;
338		}
339
340		// distribute error using this pattern:
341		//        0 X 7 (current_error)
342		// (left) 3 5 1 (right)
343		//       (middle)
344		int* right = &errorTable[x+1];
345		current_error = (*right) + 7 * error;
346		*right = 1 * error;
347
348		int* middle = right - 1;
349		*middle += 5 * error;
350
351		int* left = middle - 1;
352		*left += 3 * error;
353
354		if (bit == 0) {
355			*destination = ConvertUsingBlackValue(cur);
356			// advance to next byte
357			destination ++;
358			cur = 0;
359		}
360	}
361
362	const bool hasRest = (width % 8) != 0;
363	if (hasRest) {
364		*destination = ConvertUsingBlackValue(cur);
365	}
366}
367
368