⛏️ index : haiku.git

/*
 * Copyright 2006-2007, 2023, Haiku. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Stephan Aßmus <superstippi@gmx.de>
 *		Zardshard
 */

#include "IconRenderer.h"

#include <algorithm>
#include <new>
#include <stdio.h>

#include <Bitmap.h>
#include <List.h>

#include <agg_image_accessors.h>
#include <agg_span_image_filter_rgba.h>
#include <agg_span_gradient.h>
#include <agg_span_interpolator_linear.h>
#include <agg_span_interpolator_trans.h>

#include "CompoundStyleTransformer.h"
#include "GradientTransformable.h"
#include "Icon.h"
#include "Shape.h"
#include "Style.h"
#include "StyleTransformer.h"
#include "VectorPath.h"

using std::nothrow;

class IconRenderer::StyleHandler {
	struct StyleItem {
		const Style*			style;
		StyleTransformer*		transformer;
	};

 public:
#ifdef ICON_O_MATIC
								StyleHandler(::GammaTable& gammaTable,
									bool showReferences);
#else
								StyleHandler(::GammaTable& gammaTable);
#endif

								~StyleHandler();

			bool				AddStyle(const Style* style,
									const Transformable& transformation,
									const Container<Transformer>* transformers);

			bool				is_solid(unsigned styleIndex) const;
			const agg::rgba8&	color(unsigned styleIndex);
			void				generate_span(agg::rgba8* span, int x, int y,
									unsigned len, unsigned styleIndex);
 private:
			StyleTransformer*	_MergeTransformers(
									const Transformable* styleTransformation,
									const Container<Transformer>* transformers,
									const Transformable* shapeTransformation);

			template<class GradientFunction>
			void				_GenerateGradient(agg::rgba8* span,
									int x, int y, unsigned len,
									GradientFunction function,
									int32 start, int32 end,
									const agg::rgba8* gradientColors,
									StyleTransformer* transformer);
#ifdef ICON_O_MATIC
			void				_GenerateImage(agg::rgba8* span,
									int x, int y, unsigned len,
									const Style* style,
									StyleTransformer* transformer);
#endif

 private:
			BList					fStyles;
			::GammaTable&			fGammaTable;
#ifdef ICON_O_MATIC
			bool					fShowReferences;
#endif
			agg::rgba8				fTransparent;
			agg::rgba8				fColor;
};



#ifdef ICON_O_MATIC
IconRenderer::StyleHandler::StyleHandler(::GammaTable& gammaTable, bool showReferences)
	: fStyles(20),
	  fGammaTable(gammaTable),
	  fShowReferences(showReferences),
	  fTransparent(0, 0, 0, 0),
	  fColor(0, 0, 0, 0)
{}
#else
IconRenderer::StyleHandler::StyleHandler(::GammaTable& gammaTable)
	: fStyles(20),
	  fGammaTable(gammaTable),
	  fTransparent(0, 0, 0, 0),
	  fColor(0, 0, 0, 0)
{}
#endif


IconRenderer::StyleHandler::~StyleHandler()
{
	int32 count = fStyles.CountItems();
	for (int32 i = 0; i < count; i++) {
		StyleItem* item = (StyleItem*)fStyles.ItemAtFast(i);
		delete item->transformer;
		delete item;
	}
}


bool
IconRenderer::StyleHandler::AddStyle(const Style* style,
	const Transformable& transformation, const Container<Transformer>* transformers)
{
	if (!style)
		return false;

	StyleItem* item = new (nothrow) StyleItem;
	if (!item)
		return false;

	item->style = style;

	item->transformer = _MergeTransformers(style->Gradient(), transformers, &transformation);
	item->transformer->Invert();

	return fStyles.AddItem((void*)item);
}


bool
IconRenderer::StyleHandler::is_solid(unsigned styleIndex) const
{
	StyleItem* styleItem = (StyleItem*)fStyles.ItemAt(styleIndex);
	if (!styleItem)
		return true;

	if (styleItem->style->Gradient())
		return false;

#ifdef ICON_O_MATIC
	if (styleItem->style->Bitmap() && fShowReferences)
		return false;
#endif // ICON_O_MATIC

	return true;
}


const agg::rgba8&
IconRenderer::StyleHandler::color(unsigned styleIndex)
{
	StyleItem* styleItem = (StyleItem*)fStyles.ItemAt(styleIndex);
	if (!styleItem) {
		printf("no style at index: %u!\n", styleIndex);
		return fTransparent;
	}

#ifdef ICON_O_MATIC
	if (styleItem->style->Bitmap() && !fShowReferences) {
		fColor = agg::rgba8(0,0,0,0);
		return fColor;
	}
#endif

	const rgb_color& c = styleItem->style->Color();
	fColor = agg::rgba8(fGammaTable.dir(c.red), fGammaTable.dir(c.green),
		fGammaTable.dir(c.blue), c.alpha);
	fColor.premultiply();
    return fColor;
}


void
IconRenderer::StyleHandler::generate_span(agg::rgba8* span, int x, int y,
	unsigned len, unsigned styleIndex)
{
	StyleItem* styleItem = (StyleItem*)fStyles.ItemAt(styleIndex);
	if (!styleItem
			|| (!styleItem->style->Gradient()
#ifdef ICON_O_MATIC
				&& !styleItem->style->Bitmap()
#endif
		)) {
		printf("no style/gradient at index: %u!\n", styleIndex);
		// TODO: memset() span?
		return;
	}

#ifdef ICON_O_MATIC
	if (styleItem->style->Bitmap()) {
		_GenerateImage(span, x, y, len, styleItem->style, styleItem->transformer);
		return;
	}
#endif // ICON_O_MATIC

	const Style* style = styleItem->style;
	Gradient* gradient = style->Gradient();
	const agg::rgba8* colors = style->GammaCorrectedColors(fGammaTable);

	switch (gradient->Type()) {
		case GRADIENT_LINEAR: {
		    agg::gradient_x function;
			_GenerateGradient(span, x, y, len, function, -64, 64, colors,
				styleItem->transformer);
			break;
		}
		case GRADIENT_CIRCULAR: {
		    agg::gradient_radial function;
			_GenerateGradient(span, x, y, len, function, 0, 64, colors,
				styleItem->transformer);
			break;
		}
		case GRADIENT_DIAMOND: {
		    agg::gradient_diamond function;
			_GenerateGradient(span, x, y, len, function, 0, 64, colors,
				styleItem->transformer);
			break;
		}
		case GRADIENT_CONIC: {
		    agg::gradient_conic function;
			_GenerateGradient(span, x, y, len, function, 0, 64, colors,
				styleItem->transformer);
			break;
		}
		case GRADIENT_XY: {
		    agg::gradient_xy function;
			_GenerateGradient(span, x, y, len, function, 0, 64, colors,
				styleItem->transformer);
			break;
		}
		case GRADIENT_SQRT_XY: {
		    agg::gradient_sqrt_xy function;
			_GenerateGradient(span, x, y, len, function, 0, 64, colors,
				styleItem->transformer);
			break;
		}
	}
}


StyleTransformer*
IconRenderer::StyleHandler::_MergeTransformers(const Transformable* styleTransformation,
	const Container<Transformer>* transformers, const Transformable* shapeTransformation)
{
	// Figure out how large to make the array
	int32 count = 0;
	if (styleTransformation != NULL)
		count++;
	for (int i = 0; i < transformers->CountItems(); i++) {
		if (dynamic_cast<StyleTransformer*>(transformers->ItemAtFast(i)))
			count++;
	}
	count++;

	// Populate the array
	StyleTransformer** styleTransformers = new (nothrow) StyleTransformer*[count];
	if (styleTransformers == NULL)
		return NULL;

	int i = 0;
	if (styleTransformation != NULL)
		styleTransformers[i++] = new (nothrow) Transformable(*styleTransformation);
	for (int j = 0; j < transformers->CountItems(); j++) {
		Transformer* transformer = transformers->ItemAtFast(j);
		if (dynamic_cast<StyleTransformer*>(transformer) != NULL) {
			styleTransformers[i++]
				= dynamic_cast<StyleTransformer*>(transformer->Clone());
		}
	}
	styleTransformers[i++] = new (nothrow) Transformable(*shapeTransformation);

	CompoundStyleTransformer* styleTransformer
		= new (nothrow) CompoundStyleTransformer(styleTransformers, count);
	if (styleTransformer == NULL) {
		delete[] styleTransformers;
		return NULL;
	}

	return styleTransformer;
}


template<class GradientFunction>
void
IconRenderer::StyleHandler::_GenerateGradient(agg::rgba8* span, int x, int y,
	unsigned len, GradientFunction function, int32 start, int32 end,
	const agg::rgba8* gradientColors, StyleTransformer* transformer)
{
	// TODO: performance could potentially be improved by avoiding recreating
	// these objects on every span
	typedef agg::pod_auto_array<agg::rgba8, 256>	ColorArray;
	ColorArray array(gradientColors);

	if (transformer->IsLinear()) {
		typedef agg::span_interpolator_linear
			<StyleTransformer>						Interpolator;
		typedef agg::span_gradient<agg::rgba8,
								Interpolator,
								GradientFunction,
								ColorArray>			GradientGenerator;

		Interpolator interpolator(*transformer);

		GradientGenerator gradientGenerator(interpolator, function, array,
			start, end);

		gradientGenerator.generate(span, x, y, len);
	} else {
		typedef agg::span_interpolator_trans
			<StyleTransformer>						Interpolator;
		typedef agg::span_gradient<agg::rgba8,
								Interpolator,
								GradientFunction,
								ColorArray>			GradientGenerator;

		Interpolator interpolator(*transformer);

		GradientGenerator gradientGenerator(interpolator, function, array,
			start, end);

		gradientGenerator.generate(span, x, y, len);
	}
}


#ifdef ICON_O_MATIC
void
IconRenderer::StyleHandler::_GenerateImage(agg::rgba8* span, int x, int y,
	unsigned len, const Style* style, StyleTransformer* transformer)
{
	// bitmap
	BBitmap* bbitmap = style->Bitmap();
	agg::rendering_buffer bitmap;
	bitmap.attach(static_cast<unsigned char*>(bbitmap->Bits()), bbitmap->Bounds().Width() + 1,
		bbitmap->Bounds().Height() + 1, bbitmap->BytesPerRow());

	// pixel format attached to bitmap
	PixelFormat pixf_img(bitmap);

	// image interpolator
	// TODO: performance could be improved by using agg_interpolator_linear
	//       where possible, similar to what _GenerateGradient does.
	typedef agg::span_interpolator_trans<StyleTransformer>
		interpolator_type;
	interpolator_type interpolator(*transformer);

	// image accessor attached to pixel format of bitmap
	typedef agg::image_accessor_clip<PixelFormat> source_type;
	agg::rgba8 background(0, 0, 0, 0);
	source_type source(pixf_img, background);

	// image filter (nearest neighbor)
	typedef agg::span_image_filter_rgba_nn<
		source_type, interpolator_type> span_gen_type;
	span_gen_type spanGenerator(source, interpolator);

	// generate the requested span
	spanGenerator.generate(span, x, y, len);

	// apply postprocessing
	for (unsigned i = 0; i < len; i++) {
		span[i].apply_gamma_dir(fGammaTable);
		span[i].a = (uint8) ((float) span[i].a * style->Alpha() / 255);
		span[i].premultiply();
	}
}
#endif // ICON_O_MATIC


// #pragma mark -


class HintingTransformer {
 public:

	void transform(double* x, double* y) const
	{
		*x = floor(*x + 0.5);
		*y = floor(*y + 0.5);
	}
};


// #pragma mark -


IconRenderer::IconRenderer(BBitmap* bitmap)
	: fBitmap(bitmap),
	  fBackground(NULL),
	  fBackgroundColor(0, 0, 0, 0),
	  fIcon(NULL),

	  fGammaTable(2.2),

	  fRenderingBuffer(),
	  fPixelFormat(fRenderingBuffer),
	  fPixelFormatPre(fRenderingBuffer),
	  fBaseRenderer(fPixelFormat),
	  fBaseRendererPre(fPixelFormatPre),

	  fScanline(),
	  fBinaryScanline(),
	  fSpanAllocator(),

	  fRasterizer(),

	  fGlobalTransform()
{
	// attach rendering buffer to bitmap
	fRenderingBuffer.attach((uint8*)bitmap->Bits(),
		bitmap->Bounds().IntegerWidth() + 1,
		bitmap->Bounds().IntegerHeight() + 1, bitmap->BytesPerRow());

	fBaseRendererPre.clip_box(0, 0, bitmap->Bounds().IntegerWidth(),
		bitmap->Bounds().IntegerHeight());
}


IconRenderer::~IconRenderer()
{
}


void
IconRenderer::SetIcon(const Icon* icon)
{
	if (fIcon == icon)
		return;

	fIcon = icon;
	// TODO: ... ?
}


void
#ifdef ICON_O_MATIC
IconRenderer::Render(bool showReferences)
#else
IconRenderer::Render()
#endif
{
#ifdef ICON_O_MATIC
	_Render(fBitmap->Bounds(), showReferences);
#else
	_Render(fBitmap->Bounds());
#endif
}


void
#ifdef ICON_O_MATIC
IconRenderer::Render(const BRect& area, bool showReferences)
#else
IconRenderer::Render(const BRect& area)
#endif
{
#ifdef ICON_O_MATIC
	_Render(fBitmap->Bounds() & area, showReferences);
#else
	_Render(fBitmap->Bounds() & area);
#endif
}


void
IconRenderer::SetScale(double scale)
{
	fGlobalTransform.reset();
	fGlobalTransform.multiply(agg::trans_affine_scaling(scale));
}


void
IconRenderer::SetBackground(const BBitmap* background)
{
	fBackground = background;
}


void
IconRenderer::SetBackground(const agg::rgba8& background)
{
	fBackgroundColor.r = fGammaTable.dir(background.r);
	fBackgroundColor.g = fGammaTable.dir(background.g);
	fBackgroundColor.b = fGammaTable.dir(background.b);
	fBackgroundColor.a = background.a;
}


void
IconRenderer::Demultiply()
{
	uint8* bits = (uint8*)fBitmap->Bits();
	uint32 bpr = fBitmap->BytesPerRow();
	uint32 width = fBitmap->Bounds().IntegerWidth() + 1;
	uint32 height = fBitmap->Bounds().IntegerHeight() + 1;

	for (uint32 y = 0; y < height; y++) {
		uint8* b = bits;
		for (uint32 x = 0; x < width; x++) {
			if (b[3] < 255 && b[3] > 0) {
				b[0] = (uint8)((int)b[0] * 255 / b[3]);
				b[1] = (uint8)((int)b[1] * 255 / b[3]);
				b[2] = (uint8)((int)b[2] * 255 / b[3]);
			}
			b += 4;
		}
		bits += bpr;
	}
}


// #pragma mark -


typedef agg::conv_transform<VertexSource, Transformable> ScaledPath;
typedef agg::conv_transform<ScaledPath, HintingTransformer> HintedPath;


void
#ifdef ICON_O_MATIC
IconRenderer::_Render(const BRect& r, bool showReferences)
#else
IconRenderer::_Render(const BRect& r)
#endif
{
	if (!fIcon)
		return;

// TODO: fix clip box for "clear" and "apply_gamma_inv"
//	fBaseRendererPre.clip_box((int)floorf(r.left), (int)floorf(r.top),
//		(int)ceilf(r.right), (int)ceilf(r.bottom));

	if (fBackground)
		memcpy(fBitmap->Bits(), fBackground->Bits(), fBitmap->BitsLength());
	else
		fBaseRendererPre.clear(fBackgroundColor);

//bigtime_t start = system_time();
#ifdef ICON_O_MATIC
	StyleHandler styleHandler(fGammaTable, showReferences);
#else
	StyleHandler styleHandler(fGammaTable);
#endif

	fRasterizer.reset();
	// iterate over the shapes in the icon,
	// add the vector paths to the rasterizer
	// and associate each shapes style
	int32 shapeCount = fIcon->Shapes()->CountItems();
	int32 styleIndex = 0;
	for (int32 i = 0; i < shapeCount; i++) {
		Shape* shape = fIcon->Shapes()->ItemAtFast(i);

		if (!shape->Visible(fGlobalTransform.scale())) {
			continue;
		}

		Transformable transform(*shape);
		transform.multiply(fGlobalTransform);

		Style* style = shape->Style();
		if (!style)
			continue;

		// add the style either with global transformation or with
		// the shapes transformation, depending on whether there
		// is a gradient and its settings
		Gradient* gradient = style->Gradient();
		bool styleAdded = false;
		if (gradient && !gradient->InheritTransformation()) {
			styleAdded = styleHandler.AddStyle(
				style, fGlobalTransform, NULL);
		} else {
			styleAdded = styleHandler.AddStyle(
				style, transform, shape->Transformers());
		}

		if (!styleAdded) {
			printf("IconRenderer::_Render() - out of memory\n");
			break;
		}

		// if this is not the first shape, and the style contains
		// transparency, commit a render pass of previous shapes
		if (i > 0
				&& (style->HasTransparency()
#ifdef ICON_O_MATIC
					|| style->Bitmap() != NULL
#endif
			))
			_CommitRenderPass(styleHandler);

		fRasterizer.styles(styleIndex, -1);
		styleIndex++;

		// global scale
		shape->SetGlobalScale(max_c(1.0, transform.scale()));
		ScaledPath scaledPath(shape->VertexSource(), transform);
		if (shape->Hinting()) {
			// additional hinting
			HintingTransformer hinter;
			HintedPath hintedPath(scaledPath, hinter);
			fRasterizer.add_path(hintedPath);
		} else {
			fRasterizer.add_path(scaledPath);
		}
	}

	_CommitRenderPass(styleHandler, false);

	if (fGammaTable.gamma() != 1.0)
		fPixelFormat.apply_gamma_inv(fGammaTable);

//if (fRenderingBuffer.width() == 64)
//printf("rendering 64x64: %lld\n", system_time() - start);
}


void
IconRenderer::_CommitRenderPass(StyleHandler& styleHandler, bool reset)
{
	agg::render_scanlines_compound(fRasterizer, fScanline, fBinaryScanline,
		fBaseRendererPre, fSpanAllocator, styleHandler);

	if (reset)
		fRasterizer.reset();
}