xref: /haiku/src/servers/app/drawing/Painter/bitmap_painter/BitmapPainter.cpp (revision c63d300251dcbdf076ea07c2d5ccd19157882328)
1 /*
2  * Copyright 2009, Christian Packmann.
3  * Copyright 2008, Andrej Spielmann <andrej.spielmann@seh.ox.ac.uk>.
4  * Copyright 2005-2014, Stephan Aßmus <superstippi@gmx.de>.
5  * Copyright 2015, Julian Harnath <julian.harnath@rwth-aachen.de>
6  * All rights reserved. Distributed under the terms of the MIT License.
7  */
8 #include "BitmapPainter.h"
9 
10 #include <Bitmap.h>
11 
12 #include <agg_image_accessors.h>
13 #include <agg_pixfmt_rgba.h>
14 #include <agg_span_image_filter_rgba.h>
15 
16 #include "DrawBitmapBilinear.h"
17 #include "DrawBitmapGeneric.h"
18 #include "DrawBitmapNearestNeighbor.h"
19 #include "DrawBitmapNoScale.h"
20 #include "drawing_support.h"
21 #include "ServerBitmap.h"
22 #include "SystemPalette.h"
23 
24 
25 // #define TRACE_BITMAP_PAINTER
26 #ifdef TRACE_BITMAP_PAINTER
27 #	define TRACE(x...)		printf(x)
28 #else
29 #	define TRACE(x...)
30 #endif
31 
32 
BitmapPainter(const Painter * painter,const ServerBitmap * bitmap,uint32 options)33 Painter::BitmapPainter::BitmapPainter(const Painter* painter,
34 	const ServerBitmap* bitmap, uint32 options)
35 	:
36 	fPainter(painter),
37 	fStatus(B_NO_INIT),
38 	fOptions(options)
39 {
40 	if (bitmap == NULL || !bitmap->IsValid())
41 		return;
42 
43 	fBitmapBounds = bitmap->Bounds();
44 	fBitmapBounds.OffsetBy(-fBitmapBounds.left, -fBitmapBounds.top);
45 		// Compensate for the lefttop offset the bitmap bounds might have
46 		// It has the right size, but put it at B_ORIGIN
47 
48 	fColorSpace = bitmap->ColorSpace();
49 
50 	fBitmap.attach(bitmap->Bits(), bitmap->Width(), bitmap->Height(),
51 		bitmap->BytesPerRow());
52 
53 	fStatus = B_OK;
54 }
55 
56 
57 void
Draw(const BRect & sourceRect,const BRect & destinationRect)58 Painter::BitmapPainter::Draw(const BRect& sourceRect,
59 	const BRect& destinationRect)
60 {
61 	using namespace BitmapPainterPrivate;
62 
63 	if (fStatus != B_OK)
64 		return;
65 
66 	TRACE("BitmapPainter::Draw()\n");
67 	TRACE("   bitmapBounds = (%.1f, %.1f) - (%.1f, %.1f)\n",
68 		fBitmapBounds.left, fBitmapBounds.top,
69 		fBitmapBounds.right, fBitmapBounds.bottom);
70 	TRACE("   sourceRect = (%.1f, %.1f) - (%.1f, %.1f)\n",
71 		sourceRect.left, sourceRect.top,
72 		sourceRect.right, sourceRect.bottom);
73 	TRACE("   destinationRect = (%.1f, %.1f) - (%.1f, %.1f)\n",
74 		destinationRect.left, destinationRect.top,
75 		destinationRect.right, destinationRect.bottom);
76 
77 	bool success = _DetermineTransform(sourceRect, destinationRect);
78 	if (!success)
79 		return;
80 
81 	if ((fOptions & B_TILE_BITMAP) == 0) {
82 		// optimized version for no scale in CMAP8 or RGB32 OP_OVER
83 		if (!_HasScale() && !_HasAffineTransform() && !_HasAlphaMask()) {
84 			if (fColorSpace == B_CMAP8) {
85 				if (fPainter->fDrawingMode == B_OP_COPY) {
86 					DrawBitmapNoScale<CMap8Copy> drawNoScale;
87 					drawNoScale.Draw(fPainter->fInternal, fBitmap, 1, fOffset,
88 						fDestinationRect);
89 					return;
90 				}
91 				if (fPainter->fDrawingMode == B_OP_OVER) {
92 					DrawBitmapNoScale<CMap8Over> drawNoScale;
93 					drawNoScale.Draw(fPainter->fInternal, fBitmap, 1, fOffset,
94 						fDestinationRect);
95 					return;
96 				}
97 			} else if (fColorSpace == B_RGB32) {
98 				if (fPainter->fDrawingMode == B_OP_OVER) {
99 					DrawBitmapNoScale<Bgr32Over> drawNoScale;
100 					drawNoScale.Draw(fPainter->fInternal, fBitmap, 4, fOffset,
101 						fDestinationRect);
102 					return;
103 				}
104 			}
105 		}
106 	}
107 
108 	ObjectDeleter<BBitmap> convertedBitmapDeleter;
109 	_ConvertColorSpace(convertedBitmapDeleter);
110 
111 	if ((fOptions & B_TILE_BITMAP) == 0) {
112 		// optimized version if there is no scale
113 		if (!_HasScale() && !_HasAffineTransform() && !_HasAlphaMask()) {
114 			if (fPainter->fDrawingMode == B_OP_COPY) {
115 				DrawBitmapNoScale<Bgr32Copy> drawNoScale;
116 				drawNoScale.Draw(fPainter->fInternal, fBitmap, 4, fOffset,
117 					fDestinationRect);
118 				return;
119 			}
120 			if (fPainter->fDrawingMode == B_OP_OVER
121 				|| (fPainter->fDrawingMode == B_OP_ALPHA
122 					 && fPainter->fAlphaSrcMode == B_PIXEL_ALPHA
123 					 && fPainter->fAlphaFncMode == B_ALPHA_OVERLAY)) {
124 				DrawBitmapNoScale<Bgr32Alpha> drawNoScale;
125 				drawNoScale.Draw(fPainter->fInternal, fBitmap, 4, fOffset,
126 					fDestinationRect);
127 				return;
128 			}
129 		}
130 
131 		if (!_HasScale() && !_HasAffineTransform() && _HasAlphaMask()) {
132 			if (fPainter->fDrawingMode == B_OP_COPY) {
133 				DrawBitmapNoScale<Bgr32CopyMasked> drawNoScale;
134 				drawNoScale.Draw(fPainter->fInternal, fBitmap, 4, fOffset,
135 					fDestinationRect);
136 				return;
137 			}
138 		}
139 
140 		// bilinear and nearest-neighbor scaled, OP_COPY only
141 		if (fPainter->fDrawingMode == B_OP_COPY
142 			&& !_HasAffineTransform() && !_HasAlphaMask()) {
143 			if ((fOptions & B_FILTER_BITMAP_BILINEAR) != 0) {
144 				DrawBitmapBilinear<ColorTypeRgb, DrawModeCopy> drawBilinear;
145 				drawBilinear.Draw(fPainter, fPainter->fInternal,
146 					fBitmap, fOffset, fScaleX, fScaleY, fDestinationRect);
147 			} else {
148 				DrawBitmapNearestNeighborCopy::Draw(fPainter, fPainter->fInternal,
149 					fBitmap, fOffset, fScaleX, fScaleY, fDestinationRect);
150 			}
151 			return;
152 		}
153 
154 		if (fPainter->fDrawingMode == B_OP_ALPHA
155 			&& fPainter->fAlphaSrcMode == B_PIXEL_ALPHA
156 			&& fPainter->fAlphaFncMode == B_ALPHA_OVERLAY
157 			&& !_HasAffineTransform() && !_HasAlphaMask()
158 			&& (fOptions & B_FILTER_BITMAP_BILINEAR) != 0) {
159 			DrawBitmapBilinear<ColorTypeRgba, DrawModeAlphaOverlay> drawBilinear;
160 			drawBilinear.Draw(fPainter, fPainter->fInternal,
161 				fBitmap, fOffset, fScaleX, fScaleY, fDestinationRect);
162 			return;
163 		}
164 	}
165 
166 	if ((fOptions & B_TILE_BITMAP) != 0) {
167 		DrawBitmapGeneric<Tile>::Draw(fPainter, fPainter->fInternal, fBitmap,
168 			fOffset, fScaleX, fScaleY, fDestinationRect, fOptions);
169 	} else {
170 		// for all other cases (non-optimized drawing mode or scaled drawing)
171 		DrawBitmapGeneric<Fill>::Draw(fPainter, fPainter->fInternal, fBitmap,
172 			fOffset, fScaleX, fScaleY, fDestinationRect, fOptions);
173 	}
174 }
175 
176 
177 bool
_DetermineTransform(BRect sourceRect,const BRect & destinationRect)178 Painter::BitmapPainter::_DetermineTransform(BRect sourceRect,
179 	const BRect& destinationRect)
180 {
181 	if (!fPainter->fValidClipping
182 		|| !sourceRect.IsValid()
183 		|| ((fOptions & B_TILE_BITMAP) == 0
184 			&& !sourceRect.Intersects(fBitmapBounds))
185 		|| !destinationRect.IsValid()) {
186 		return false;
187 	}
188 
189 	fDestinationRect = destinationRect;
190 
191 	if (!fPainter->fSubpixelPrecise) {
192 		align_rect_to_pixels(&sourceRect);
193 		align_rect_to_pixels(&fDestinationRect);
194 	}
195 
196 	if((fOptions & B_TILE_BITMAP) == 0) {
197 		fScaleX = (fDestinationRect.Width() + 1) / (sourceRect.Width() + 1);
198 		fScaleY = (fDestinationRect.Height() + 1) / (sourceRect.Height() + 1);
199 
200 		if (fScaleX == 0.0 || fScaleY == 0.0)
201 			return false;
202 
203 		// constrain source rect to bitmap bounds and transfer the changes to
204 		// the destination rect with the right scale
205 		if (sourceRect.left < fBitmapBounds.left) {
206 			float diff = fBitmapBounds.left - sourceRect.left;
207 			fDestinationRect.left += diff * fScaleX;
208 			sourceRect.left = fBitmapBounds.left;
209 		}
210 		if (sourceRect.top < fBitmapBounds.top) {
211 			float diff = fBitmapBounds.top - sourceRect.top;
212 			fDestinationRect.top += diff * fScaleY;
213 			sourceRect.top = fBitmapBounds.top;
214 		}
215 		if (sourceRect.right > fBitmapBounds.right) {
216 			float diff = sourceRect.right - fBitmapBounds.right;
217 			fDestinationRect.right -= diff * fScaleX;
218 			sourceRect.right = fBitmapBounds.right;
219 		}
220 		if (sourceRect.bottom > fBitmapBounds.bottom) {
221 			float diff = sourceRect.bottom - fBitmapBounds.bottom;
222 			fDestinationRect.bottom -= diff * fScaleY;
223 			sourceRect.bottom = fBitmapBounds.bottom;
224 		}
225 	} else {
226 		fScaleX = 1.0;
227 		fScaleY = 1.0;
228 	}
229 
230 	fOffset.x = fDestinationRect.left - sourceRect.left;
231 	fOffset.y = fDestinationRect.top - sourceRect.top;
232 
233 	return true;
234 }
235 
236 
237 bool
_HasScale()238 Painter::BitmapPainter::_HasScale()
239 {
240 	return fScaleX != 1.0 || fScaleY != 1.0;
241 }
242 
243 
244 bool
_HasAffineTransform()245 Painter::BitmapPainter::_HasAffineTransform()
246 {
247 	return !fPainter->fIdentityTransform;
248 }
249 
250 
251 bool
_HasAlphaMask()252 Painter::BitmapPainter::_HasAlphaMask()
253 {
254 	return fPainter->fInternal.fMaskedUnpackedScanline != NULL;
255 }
256 
257 
258 void
_ConvertColorSpace(ObjectDeleter<BBitmap> & convertedBitmapDeleter)259 Painter::BitmapPainter::_ConvertColorSpace(
260 	ObjectDeleter<BBitmap>& convertedBitmapDeleter)
261 {
262 	if (fColorSpace == B_RGBA32)
263 		return;
264 
265 	if (fColorSpace == B_RGB32
266 		&& (fPainter->fDrawingMode == B_OP_COPY
267 #if 1
268 // Enabling this would make the behavior compatible to BeOS, which
269 // treats B_RGB32 bitmaps as B_RGB*A*32 bitmaps in B_OP_ALPHA - unlike in
270 // all other drawing modes, where B_TRANSPARENT_MAGIC_RGBA32 is handled.
271 // B_RGB32 bitmaps therefore don't draw correctly on BeOS if they actually
272 // use this color, unless the alpha channel contains 255 for all other
273 // pixels, which is inconsistent.
274 		|| fPainter->fDrawingMode == B_OP_ALPHA
275 #endif
276 		)) {
277 		return;
278 	}
279 
280 	BBitmap* conversionBitmap = new(std::nothrow) BBitmap(fBitmapBounds,
281 		B_BITMAP_NO_SERVER_LINK, B_RGBA32);
282 	if (conversionBitmap == NULL) {
283 		fprintf(stderr, "BitmapPainter::_ConvertColorSpace() - "
284 			"out of memory for creating temporary conversion bitmap\n");
285 		return;
286 	}
287 	convertedBitmapDeleter.SetTo(conversionBitmap);
288 
289 	status_t err = conversionBitmap->ImportBits(fBitmap.buf(),
290 		fBitmap.height() * fBitmap.stride(),
291 		fBitmap.stride(), 0, fColorSpace);
292 	if (err < B_OK) {
293 		fprintf(stderr, "BitmapPainter::_ConvertColorSpace() - "
294 			"colorspace conversion failed: %s\n", strerror(err));
295 		return;
296 	}
297 
298 	// the original bitmap might have had some of the
299 	// transaparent magic colors set that we now need to
300 	// make transparent in our RGBA32 bitmap again.
301 	switch (fColorSpace) {
302 		case B_RGB32:
303 			_TransparentMagicToAlpha((uint32 *)fBitmap.buf(),
304 				fBitmap.width(), fBitmap.height(),
305 				fBitmap.stride(), B_TRANSPARENT_MAGIC_RGBA32,
306 				conversionBitmap);
307 			break;
308 
309 		// TODO: not sure if this applies to B_RGBA15 too. It
310 		// should not because B_RGBA15 actually has an alpha
311 		// channel itself and it should have been preserved
312 		// when importing the bitmap. Maybe it applies to
313 		// B_RGB16 though?
314 		case B_RGB15:
315 			_TransparentMagicToAlpha((uint16 *)fBitmap.buf(),
316 				fBitmap.width(), fBitmap.height(),
317 				fBitmap.stride(), B_TRANSPARENT_MAGIC_RGBA15,
318 				conversionBitmap);
319 			break;
320 
321 		default:
322 			break;
323 	}
324 
325 	fBitmap.attach((uint8*)conversionBitmap->Bits(),
326 		(uint32)fBitmapBounds.IntegerWidth() + 1,
327 		(uint32)fBitmapBounds.IntegerHeight() + 1,
328 		conversionBitmap->BytesPerRow());
329 }
330 
331 
332 template<typename sourcePixel>
333 void
_TransparentMagicToAlpha(sourcePixel * buffer,uint32 width,uint32 height,uint32 sourceBytesPerRow,sourcePixel transparentMagic,BBitmap * output)334 Painter::BitmapPainter::_TransparentMagicToAlpha(sourcePixel* buffer,
335 	uint32 width, uint32 height, uint32 sourceBytesPerRow,
336 	sourcePixel transparentMagic, BBitmap* output)
337 {
338 	uint8* sourceRow = (uint8*)buffer;
339 	uint8* destRow = (uint8*)output->Bits();
340 	uint32 destBytesPerRow = output->BytesPerRow();
341 
342 	for (uint32 y = 0; y < height; y++) {
343 		sourcePixel* pixel = (sourcePixel*)sourceRow;
344 		uint32* destPixel = (uint32*)destRow;
345 		for (uint32 x = 0; x < width; x++, pixel++, destPixel++) {
346 			if (*pixel == transparentMagic)
347 				*destPixel &= 0x00ffffff;
348 		}
349 
350 		sourceRow += sourceBytesPerRow;
351 		destRow += destBytesPerRow;
352 	}
353 }
354