⛏️ index : haiku.git

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

#include "FlatIconExporter.h"

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

#include <Archivable.h>
#include <Catalog.h>
#include <DataIO.h>
#include <Locale.h>
#include <Message.h>
#include <Node.h>

#include "AffineTransformer.h"
#include "Container.h"
#include "ContourTransformer.h"
#include "FlatIconFormat.h"
#include "GradientTransformable.h"
#include "Icon.h"
#include "LittleEndianBuffer.h"
#include "PathCommandQueue.h"
#include "PathSourceShape.h"
#include "PerspectiveTransformer.h"
#include "ReferenceImage.h"
#include "Shape.h"
#include "StrokeTransformer.h"
#include "Style.h"
#include "VectorPath.h"

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Icon-O-Matic-FlatIconExporter"

using std::nothrow;


FlatIconExporter::FlatIconExporter()
#if PRINT_STATISTICS
	: fStyleSectionSize(0)
	, fGradientSize(0)
	, fGradientTransformSize(0)
	, fPathSectionSize(0)
	, fShapeSectionSize(0)
	, fPointCount(0)
#endif // PRINT_STATISTICS
{
}


FlatIconExporter::~FlatIconExporter()
{
#if PRINT_STATISTICS
	printf("Statistics\n"
		   "--style section size: %" B_PRId32 "\n"
		   "           gradients: %" B_PRId32 "\n"
		   " gradient transforms: %" B_PRId32 "\n"
		   "---path section size: %" B_PRId32 "\n"
		   "--shape section size: %" B_PRId32 "\n"
		   "---total/different points: %" B_PRId32 "/%" B_PRIdSSIZE "\n",
		   fStyleSectionSize,
		   fGradientSize,
		   fGradientTransformSize,
		   fPathSectionSize,
		   fShapeSectionSize,
		   fPointCount,
		   fUsedPoints.size());
#endif // PRINT_STATISTICS
}


status_t
FlatIconExporter::Export(const Icon* icon, BPositionIO* stream)
{
	LittleEndianBuffer buffer;

	// flatten icon
	status_t ret = _Export(buffer, icon);
	if (ret < B_OK)
		return ret;

	// write buffer to stream
	ssize_t written = stream->Write(buffer.Buffer(), buffer.SizeUsed());
	if (written != (ssize_t)buffer.SizeUsed()) {
		if (written < 0)
			return (status_t)written;
		return B_ERROR;
	}

	return B_OK;
}


const char*
FlatIconExporter::ErrorCodeToString(status_t code)
{
	switch (code) {
		case E_TOO_MANY_PATHS:
			return B_TRANSLATE("There are too many paths. "
				"The HVIF format supports a maximum of 255.");
		case E_PATH_TOO_MANY_POINTS:
			return B_TRANSLATE("One or more of the paths have too many vertices. "
				"The HVIF format supports a maximum of 255 vertices per path.");
		case E_TOO_MANY_SHAPES:
			return B_TRANSLATE("There are too many shapes. "
				"The HVIF format supports a maximum of 255.");
		case E_SHAPE_TOO_MANY_PATHS:
			return B_TRANSLATE("One or more of the shapes has too many paths. "
				"The HVIF format supports a maximum of 255 paths per shape.");
		case E_SHAPE_TOO_MANY_TRANSFORMERS:
			return B_TRANSLATE("One or more of the shapes have too many transformers. "
				"The HVIF format supports a maximum of 255 transformers per shape.");
		case E_TOO_MANY_STYLES:
			return B_TRANSLATE("There are too many styles. "
				"The HVIF format supports a maximum of 255.");
		default:
			return Exporter::ErrorCodeToString(code);
	}
}


// #pragma mark -


status_t
FlatIconExporter::Export(const Icon* icon, BNode* node,
						 const char* attrName)
{
	LittleEndianBuffer buffer;

	// flatten icon
	status_t ret = _Export(buffer, icon);
	if (ret < B_OK) {
		printf("failed to export to buffer: %s\n", strerror(ret));
		return ret;
	}

#ifndef __HAIKU__
	// work arround a BFS bug, attributes with the same name but different
	// type fail to be written
	node->RemoveAttr(attrName);
#endif

	// write buffer to attribute
	ssize_t written = node->WriteAttr(attrName, B_VECTOR_ICON_TYPE, 0,
									  buffer.Buffer(), buffer.SizeUsed());
	if (written != (ssize_t)buffer.SizeUsed()) {
		if (written < 0) {
			printf("failed to write attribute: %s\n",
				strerror((status_t)written));
			return (status_t)written;
		}
		printf("failed to write attribute\n");
		return B_ERROR;
	}

	return B_OK;
}


// #pragma mark -


status_t
FlatIconExporter::_Export(LittleEndianBuffer& buffer, const Icon* icon)
{
	if (!buffer.Write(FLAT_ICON_MAGIC))
		return B_NO_MEMORY;

#if PRINT_STATISTICS
	fGradientSize = 0;
	fGradientTransformSize = 0;
	fPointCount = 0;
#endif

	// styles
	const Container<Style>* styles = icon->Styles();
	status_t ret = _WriteStyles(buffer, styles);
	if (ret < B_OK)
		return ret;

#if PRINT_STATISTICS
	fStyleSectionSize = buffer.SizeUsed();
#endif

	// paths
	const Container<VectorPath>* paths = icon->Paths();
	ret = _WritePaths(buffer, paths);
	if (ret < B_OK)
		return ret;

#if PRINT_STATISTICS
	fPathSectionSize = buffer.SizeUsed() - fStyleSectionSize;
#endif

	// shapes
	ret = _WriteShapes(buffer, styles, paths, icon->Shapes());
	if (ret < B_OK)
		return ret;

#if PRINT_STATISTICS
	fShapeSectionSize = buffer.SizeUsed()
		- (fStyleSectionSize + fPathSectionSize);
#endif

	return B_OK;
}


static bool
_WriteTransformable(LittleEndianBuffer& buffer, const Transformable* transformable)
{
	int32 matrixSize = Transformable::matrix_size;
	double matrix[matrixSize];
	transformable->StoreTo(matrix);
	for (int32 i = 0; i < matrixSize; i++) {
//		if (!buffer.Write((float)matrix[i]))
//			return false;
		if (!write_float_24(buffer, (float)matrix[i]))
			return false;
	}
	return true;
}


static bool
_WriteTranslation(LittleEndianBuffer& buffer, const Transformable* transformable)
{
	BPoint t(B_ORIGIN);
	transformable->Transform(&t);
	return write_coord(buffer, t.x) && write_coord(buffer, t.y);
}


status_t
FlatIconExporter::_WriteStyles(LittleEndianBuffer& buffer, const Container<Style>* styles)
{
	if (styles->CountItems() > 255)
		return E_TOO_MANY_STYLES;
	uint8 styleCount = min_c(255, styles->CountItems());
	if (!buffer.Write(styleCount))
		return B_NO_MEMORY;

	for (int32 i = 0; i < styleCount; i++) {
		Style* style = styles->ItemAtFast(i);

		// style type
		uint8 styleType;

		const Gradient* gradient = style->Gradient();
		if (gradient) {
			styleType = STYLE_TYPE_GRADIENT;
		} else {
			rgb_color color = style->Color();
			if (color.red == color.green && color.red == color.blue) {
				// gray
				if (style->Color().alpha == 255)
					styleType = STYLE_TYPE_SOLID_GRAY_NO_ALPHA;
				else
					styleType = STYLE_TYPE_SOLID_GRAY;
			} else {
				// color
				if (style->Color().alpha == 255)
					styleType = STYLE_TYPE_SOLID_COLOR_NO_ALPHA;
				else
					styleType = STYLE_TYPE_SOLID_COLOR;
			}
		}

		if (!buffer.Write(styleType))
			return B_NO_MEMORY;

		if (styleType == STYLE_TYPE_SOLID_COLOR) {
			// solid color
			rgb_color color = style->Color();
			if (!buffer.Write(*(uint32*)&color))
				return B_NO_MEMORY;
		} else if (styleType == STYLE_TYPE_SOLID_COLOR_NO_ALPHA) {
			// solid color without alpha
			rgb_color color = style->Color();
			if (!buffer.Write(color.red)
				|| !buffer.Write(color.green)
				|| !buffer.Write(color.blue))
				return B_NO_MEMORY;
		} else if (styleType == STYLE_TYPE_SOLID_GRAY) {
			// solid gray
			rgb_color color = style->Color();
			if (!buffer.Write(color.red)
				|| !buffer.Write(color.alpha))
				return B_NO_MEMORY;
		} else if (styleType == STYLE_TYPE_SOLID_GRAY_NO_ALPHA) {
			// solid gray without alpha
			if (!buffer.Write(style->Color().red))
				return B_NO_MEMORY;
		} else if (styleType == STYLE_TYPE_GRADIENT) {
			// gradient
			if (!_WriteGradient(buffer, gradient))
				return B_NO_MEMORY;
		}
	}

	return B_OK;
}


bool
FlatIconExporter::_AnalysePath(VectorPath* path, uint8 pointCount,
	int32& straightCount, int32& lineCount, int32& curveCount)
{
	straightCount = 0;
	lineCount = 0;
	curveCount = 0;

	BPoint lastPoint(B_ORIGIN);
	for (uint32 p = 0; p < pointCount; p++) {
		BPoint point;
		BPoint pointIn;
		BPoint pointOut;
		if (!path->GetPointsAt(p, point, pointIn, pointOut))
			return B_ERROR;
		if (point == pointIn && point == pointOut) {
			if (point.x == lastPoint.x || point.y == lastPoint.y)
				straightCount++;
			else
				lineCount++;
		} else
			curveCount++;
		lastPoint = point;

#if PRINT_STATISTICS
		fUsedPoints.insert(point);
		fUsedPoints.insert(pointIn);
		fUsedPoints.insert(pointOut);
		fPointCount += 3;
#endif
	}

	return true;
}



static bool
write_path_no_curves(LittleEndianBuffer& buffer, VectorPath* path, uint8 pointCount)
{
//printf("write_path_no_curves()\n");
	for (uint32 p = 0; p < pointCount; p++) {
		BPoint point;
		if (!path->GetPointAt(p, point))
			return false;
		if (!write_coord(buffer, point.x) || !write_coord(buffer, point.y))
			return false;
	}
	return true;
}


static bool
write_path_curves(LittleEndianBuffer& buffer, VectorPath* path, uint8 pointCount)
{
//printf("write_path_curves()\n");
	for (uint32 p = 0; p < pointCount; p++) {
		BPoint point;
		BPoint pointIn;
		BPoint pointOut;
		if (!path->GetPointsAt(p, point, pointIn, pointOut))
			return false;
		if (!write_coord(buffer, point.x)
			|| !write_coord(buffer, point.y))
			return false;
		if (!write_coord(buffer, pointIn.x)
			|| !write_coord(buffer, pointIn.y))
			return false;
		if (!write_coord(buffer, pointOut.x)
			|| !write_coord(buffer, pointOut.y))
			return false;
	}
	return true;
}


static bool
write_path_with_commands(LittleEndianBuffer& buffer, VectorPath* path, uint8 pointCount)
{
	PathCommandQueue queue;
	return queue.Write(buffer, path, pointCount);
}



status_t
FlatIconExporter::_WritePaths(LittleEndianBuffer& buffer, const Container<VectorPath>* paths)
{
	if (paths->CountItems() > 255)
		return E_TOO_MANY_PATHS;
	uint8 pathCount = min_c(255, paths->CountItems());
	if (!buffer.Write(pathCount))
		return B_NO_MEMORY;

	for (uint32 i = 0; i < pathCount; i++) {
		VectorPath* path = paths->ItemAtFast(i);
		uint8 pathFlags = 0;
		if (path->IsClosed())
			pathFlags |= PATH_FLAG_CLOSED;

		if (path->CountPoints() > 255)
			return E_PATH_TOO_MANY_POINTS;
		uint8 pointCount = min_c(255, path->CountPoints());

		// see if writing segments with commands is more efficient
		// than writing all segments as curves with no commands
		int32 straightCount;
		int32 lineCount;
		int32 curveCount;
		if (!_AnalysePath(path, pointCount,
			 	straightCount, lineCount, curveCount))
			 return B_ERROR;

		int32 commandPathLength
			= pointCount + straightCount * 2 + lineCount * 4 + curveCount * 12;
		int32 plainPathLength
			= pointCount * 12;
//printf("segments: %d, command length: %ld, plain length: %ld\n",
//	pointCount, commandPathLength, plainPathLength);

		if (commandPathLength < plainPathLength) {
			if (curveCount == 0)
				pathFlags |= PATH_FLAG_NO_CURVES;
			else
				pathFlags |= PATH_FLAG_USES_COMMANDS;
		}

		if (!buffer.Write(pathFlags) || !buffer.Write(pointCount))
			return B_NO_MEMORY;

		if (pathFlags & PATH_FLAG_NO_CURVES) {
			if (!write_path_no_curves(buffer, path, pointCount))
				return B_ERROR;
		} else if (pathFlags & PATH_FLAG_USES_COMMANDS) {
			if (!write_path_with_commands(buffer, path, pointCount))
				return B_ERROR;
		} else {
			if (!write_path_curves(buffer, path, pointCount))
				return B_ERROR;
		}
	}

	return B_OK;
}


static bool
_WriteTransformer(LittleEndianBuffer& buffer, Transformer* t)
{
	if (AffineTransformer* affine
		= dynamic_cast<AffineTransformer*>(t)) {
		// affine
		if (!buffer.Write((uint8)TRANSFORMER_TYPE_AFFINE))
			return false;
		double matrix[6];
		affine->store_to(matrix);
		for (int32 i = 0; i < 6; i++) {
			if (!write_float_24(buffer, (float)matrix[i]))
				return false;
		}

	} else if (ContourTransformer* contour
		= dynamic_cast<ContourTransformer*>(t)) {
		// contour
		if (!buffer.Write((uint8)TRANSFORMER_TYPE_CONTOUR))
			return false;
		uint8 width = (uint8)((int8)contour->width() + 128);
		uint8 lineJoin = (uint8)contour->line_join();
		uint8 miterLimit = (uint8)contour->miter_limit();
		if (!buffer.Write(width)
			|| !buffer.Write(lineJoin)
			|| !buffer.Write(miterLimit))
			return false;

	} else if (PerspectiveTransformer* perspective
		= dynamic_cast<PerspectiveTransformer*>(t)) {
		// perspective
		if (!buffer.Write((uint8)TRANSFORMER_TYPE_PERSPECTIVE))
			return false;
		double matrix[9];
		perspective->store_to(matrix);
		for (int32 i = 0; i < 9; i++) {
			if (!write_float_24(buffer, (float)matrix[i]))
				return false;
		}

	} else if (StrokeTransformer* stroke
		= dynamic_cast<StrokeTransformer*>(t)) {
		// stroke
		if (!buffer.Write((uint8)TRANSFORMER_TYPE_STROKE))
			return false;
		uint8 width = (uint8)((int8)stroke->width() + 128);
		uint8 lineOptions = (uint8)stroke->line_join();
		lineOptions |= ((uint8)stroke->line_cap()) << 4;
		uint8 miterLimit = (uint8)stroke->miter_limit();

		if (!buffer.Write(width)
			|| !buffer.Write(lineOptions)
			|| !buffer.Write(miterLimit))
			return false;
	}

	return true;
}


static bool
_WritePathSourceShape(LittleEndianBuffer& buffer, PathSourceShape* shape,
	const Container<Style>* styles, const Container<VectorPath>* paths)
{
	// find out which style this shape uses
	Style* style = shape->Style();
	if (!style)
		return false;

	int32 styleIndex = styles->IndexOf(style);
	if (styleIndex < 0 || styleIndex > 255)
		return false;

	if (shape->Paths()->CountItems() > 255)
		return E_SHAPE_TOO_MANY_PATHS;
	uint8 pathCount = min_c(255, shape->Paths()->CountItems());

	// write shape type and style index
	if (!buffer.Write((uint8)SHAPE_TYPE_PATH_SOURCE)
		|| !buffer.Write((uint8)styleIndex)
		|| !buffer.Write(pathCount))
		return false;

	// find out which paths this shape uses
	for (uint32 i = 0; i < pathCount; i++) {
		VectorPath* path = shape->Paths()->ItemAtFast(i);
		int32 pathIndex = paths->IndexOf(path);
		if (pathIndex < 0 || pathIndex > 255)
			return false;

		if (!buffer.Write((uint8)pathIndex))
			return false;
	}

	if (shape->Transformers()->CountItems() > 255)
		return E_SHAPE_TOO_MANY_TRANSFORMERS;
	uint8 transformerCount = min_c(255, shape->Transformers()->CountItems());

	// shape flags
	uint8 shapeFlags = 0;
	if (!shape->IsIdentity()) {
		if (shape->IsTranslationOnly())
			shapeFlags |= SHAPE_FLAG_TRANSLATION;
		else
			shapeFlags |= SHAPE_FLAG_TRANSFORM;
	}
	if (shape->Hinting())
		shapeFlags |= SHAPE_FLAG_HINTING;
	if (shape->MinVisibilityScale() != 0.0
		|| shape->MaxVisibilityScale() != 4.0)
		shapeFlags |= SHAPE_FLAG_LOD_SCALE;
	if (transformerCount > 0)
		shapeFlags |= SHAPE_FLAG_HAS_TRANSFORMERS;

	if (!buffer.Write((uint8)shapeFlags))
		return false;

	if (shapeFlags & SHAPE_FLAG_TRANSFORM) {
		// transformation
		if (!_WriteTransformable(buffer, shape))
			return false;
	} else if (shapeFlags & SHAPE_FLAG_TRANSLATION) {
		// translation
		if (!_WriteTranslation(buffer, shape))
			return false;
	}

	if (shapeFlags & SHAPE_FLAG_LOD_SCALE) {
		// min max visibility scale
		if (!buffer.Write(
				(uint8)(shape->MinVisibilityScale() * 63.75 + 0.5))
			|| !buffer.Write(
				(uint8)(shape->MaxVisibilityScale() * 63.75 + 0.5))) {
			return false;
		}
	}

	if (shapeFlags & SHAPE_FLAG_HAS_TRANSFORMERS) {
		// transformers
		if (!buffer.Write(transformerCount))
			return false;

		for (uint32 i = 0; i < transformerCount; i++) {
			Transformer* transformer = shape->Transformers()->ItemAtFast(i);
			if (!_WriteTransformer(buffer, transformer))
				return false;
		}
	}

	return true;
}


status_t
FlatIconExporter::_WriteShapes(LittleEndianBuffer& buffer, const Container<Style>* styles,
	const Container<VectorPath>* paths, const Container<Shape>* shapes)
{
	uint32 shapeCount = shapes->CountItems();

	// Count the number of exportable shapes
	uint32 pathShapeCount = 0;
	for (uint32 i = 0; i < shapeCount; i++) {
		Shape* shape = shapes->ItemAtFast(i);
		if (dynamic_cast<PathSourceShape*>(shape) != NULL)
			pathShapeCount++;
	}

	// Write number of exportable shapes
	if (pathShapeCount > 255)
		return E_TOO_MANY_SHAPES;
	if (!buffer.Write((uint8) pathShapeCount))
		return B_NO_MEMORY;

	// Export each shape
	for (uint32 i = 0; i < shapeCount; i++) {
		Shape* shape = shapes->ItemAtFast(i);

		PathSourceShape* pathSourceShape = dynamic_cast<PathSourceShape*>(shape);
		if (pathSourceShape != NULL) {
			if (!_WritePathSourceShape(buffer, pathSourceShape, styles, paths))
				return B_ERROR;
		}
	}

	return B_OK;
}


bool
FlatIconExporter::_WriteGradient(LittleEndianBuffer& buffer, const Gradient* gradient)
{
#if PRINT_STATISTICS
	size_t currentSize = buffer.SizeUsed();
#endif

	uint8 gradientType = (uint8)gradient->Type();
	uint8 gradientFlags = 0;
	uint8 gradientStopCount = (uint8)gradient->CountColors();

	// figure out flags
	if (!gradient->IsIdentity())
		gradientFlags |= GRADIENT_FLAG_TRANSFORM;

	bool alpha = false;
	bool gray = true;
	for (int32 i = 0; i < gradientStopCount; i++) {
		BGradient::ColorStop* step = gradient->ColorAtFast(i);
		if (step->color.alpha < 255)
			alpha = true;
		if (step->color.red != step->color.green
			|| step->color.red != step->color.blue)
			gray = false;
	}
	if (!alpha)
		gradientFlags |= GRADIENT_FLAG_NO_ALPHA;
	if (gray)
		gradientFlags |= GRADIENT_FLAG_GRAYS;

	if (!buffer.Write(gradientType)
		|| !buffer.Write(gradientFlags)
		|| !buffer.Write(gradientStopCount))
		return false;

	if (gradientFlags & GRADIENT_FLAG_TRANSFORM) {
		if (!_WriteTransformable(buffer, gradient))
			return false;
#if PRINT_STATISTICS
	fGradientTransformSize += Transformable::matrix_size * sizeof(float);
#endif
	}

	for (int32 i = 0; i < gradientStopCount; i++) {
		BGradient::ColorStop* step = gradient->ColorAtFast(i);
		uint8 stopOffset = (uint8)(step->offset * 255.0);
		uint32 color = (uint32&)step->color;
		if (!buffer.Write(stopOffset))
			return false;
		if (alpha) {
			if (gray) {
				if (!buffer.Write(step->color.red)
					|| !buffer.Write(step->color.alpha))
					return false;
			} else {
				if (!buffer.Write(color))
					return false;
			}
		} else {
			if (gray) {
				if (!buffer.Write(step->color.red))
					return false;
			} else {
				if (!buffer.Write(step->color.red)
					|| !buffer.Write(step->color.green)
					|| !buffer.Write(step->color.blue))
					return false;
			}
		}
	}

#if PRINT_STATISTICS
	fGradientSize += buffer.SizeUsed() - currentSize;
#endif

	return true;
}