* 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
{
}
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
}
status_t
FlatIconExporter::Export(const Icon* icon, BPositionIO* stream)
{
LittleEndianBuffer buffer;
status_t ret = _Export(buffer, icon);
if (ret < B_OK)
return ret;
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);
}
}
status_t
FlatIconExporter::Export(const Icon* icon, BNode* node,
const char* attrName)
{
LittleEndianBuffer buffer;
status_t ret = _Export(buffer, icon);
if (ret < B_OK) {
printf("failed to export to buffer: %s\n", strerror(ret));
return ret;
}
#ifndef __HAIKU__
node->RemoveAttr(attrName);
#endif
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;
}
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
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
const Container<VectorPath>* paths = icon->Paths();
ret = _WritePaths(buffer, paths);
if (ret < B_OK)
return ret;
#if PRINT_STATISTICS
fPathSectionSize = buffer.SizeUsed() - fStyleSectionSize;
#endif
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 (!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);
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) {
if (style->Color().alpha == 255)
styleType = STYLE_TYPE_SOLID_GRAY_NO_ALPHA;
else
styleType = STYLE_TYPE_SOLID_GRAY;
} else {
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) {
rgb_color color = style->Color();
if (!buffer.Write(*(uint32*)&color))
return B_NO_MEMORY;
} else if (styleType == STYLE_TYPE_SOLID_COLOR_NO_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) {
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) {
if (!buffer.Write(style->Color().red))
return B_NO_MEMORY;
} else if (styleType == STYLE_TYPE_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)
{
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)
{
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());
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;
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)) {
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)) {
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)) {
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)) {
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)
{
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());
if (!buffer.Write((uint8)SHAPE_TYPE_PATH_SOURCE)
|| !buffer.Write((uint8)styleIndex)
|| !buffer.Write(pathCount))
return false;
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());
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) {
if (!_WriteTransformable(buffer, shape))
return false;
} else if (shapeFlags & SHAPE_FLAG_TRANSLATION) {
if (!_WriteTranslation(buffer, shape))
return false;
}
if (shapeFlags & SHAPE_FLAG_LOD_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) {
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();
uint32 pathShapeCount = 0;
for (uint32 i = 0; i < shapeCount; i++) {
Shape* shape = shapes->ItemAtFast(i);
if (dynamic_cast<PathSourceShape*>(shape) != NULL)
pathShapeCount++;
}
if (pathShapeCount > 255)
return E_TOO_MANY_SHAPES;
if (!buffer.Write((uint8) pathShapeCount))
return B_NO_MEMORY;
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();
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;
}