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