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