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