1/* 2 * Copyright 2006, 2023, Haiku. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan A��mus <superstippi@gmx.de> 7 * Zardshard 8 */ 9 10#include "FlatIconExporter.h" 11 12#include <new> 13#include <stdio.h> 14 15#include <Archivable.h> 16#include <Catalog.h> 17#include <DataIO.h> 18#include <Locale.h> 19#include <Message.h> 20#include <Node.h> 21 22#include "AffineTransformer.h" 23#include "Container.h" 24#include "ContourTransformer.h" 25#include "FlatIconFormat.h" 26#include "GradientTransformable.h" 27#include "Icon.h" 28#include "LittleEndianBuffer.h" 29#include "PathCommandQueue.h" 30#include "PathSourceShape.h" 31#include "PerspectiveTransformer.h" 32#include "ReferenceImage.h" 33#include "Shape.h" 34#include "StrokeTransformer.h" 35#include "Style.h" 36#include "VectorPath.h" 37 38#undef B_TRANSLATION_CONTEXT 39#define B_TRANSLATION_CONTEXT "Icon-O-Matic-FlatIconExporter" 40 41using std::nothrow; 42 43 44FlatIconExporter::FlatIconExporter() 45#if PRINT_STATISTICS 46 : fStyleSectionSize(0) 47 , fGradientSize(0) 48 , fGradientTransformSize(0) 49 , fPathSectionSize(0) 50 , fShapeSectionSize(0) 51 , fPointCount(0) 52#endif // PRINT_STATISTICS 53{ 54} 55 56 57FlatIconExporter::~FlatIconExporter() 58{ 59#if PRINT_STATISTICS 60 printf("Statistics\n" 61 "--style section size: %" B_PRId32 "\n" 62 " gradients: %" B_PRId32 "\n" 63 " gradient transforms: %" B_PRId32 "\n" 64 "---path section size: %" B_PRId32 "\n" 65 "--shape section size: %" B_PRId32 "\n" 66 "---total/different points: %" B_PRId32 "/%" B_PRIdSSIZE "\n", 67 fStyleSectionSize, 68 fGradientSize, 69 fGradientTransformSize, 70 fPathSectionSize, 71 fShapeSectionSize, 72 fPointCount, 73 fUsedPoints.size()); 74#endif // PRINT_STATISTICS 75} 76 77 78status_t 79FlatIconExporter::Export(const Icon* icon, BPositionIO* stream) 80{ 81 LittleEndianBuffer buffer; 82 83 // flatten icon 84 status_t ret = _Export(buffer, icon); 85 if (ret < B_OK) 86 return ret; 87 88 // write buffer to stream 89 ssize_t written = stream->Write(buffer.Buffer(), buffer.SizeUsed()); 90 if (written != (ssize_t)buffer.SizeUsed()) { 91 if (written < 0) 92 return (status_t)written; 93 return B_ERROR; 94 } 95 96 return B_OK; 97} 98 99 100const char* 101FlatIconExporter::ErrorCodeToString(status_t code) 102{ 103 switch (code) { 104 case E_TOO_MANY_PATHS: 105 return B_TRANSLATE("There are too many paths. " 106 "The HVIF format supports a maximum of 255."); 107 case E_PATH_TOO_MANY_POINTS: 108 return B_TRANSLATE("One or more of the paths have too many vertices. " 109 "The HVIF format supports a maximum of 255 vertices per path."); 110 case E_TOO_MANY_SHAPES: 111 return B_TRANSLATE("There are too many shapes. " 112 "The HVIF format supports a maximum of 255."); 113 case E_SHAPE_TOO_MANY_PATHS: 114 return B_TRANSLATE("One or more of the shapes has too many paths. " 115 "The HVIF format supports a maximum of 255 paths per shape."); 116 case E_SHAPE_TOO_MANY_TRANSFORMERS: 117 return B_TRANSLATE("One or more of the shapes have too many transformers. " 118 "The HVIF format supports a maximum of 255 transformers per shape."); 119 case E_TOO_MANY_STYLES: 120 return B_TRANSLATE("There are too many styles. " 121 "The HVIF format supports a maximum of 255."); 122 default: 123 return Exporter::ErrorCodeToString(code); 124 } 125} 126 127 128// #pragma mark - 129 130 131status_t 132FlatIconExporter::Export(const Icon* icon, BNode* node, 133 const char* attrName) 134{ 135 LittleEndianBuffer buffer; 136 137 // flatten icon 138 status_t ret = _Export(buffer, icon); 139 if (ret < B_OK) { 140 printf("failed to export to buffer: %s\n", strerror(ret)); 141 return ret; 142 } 143 144#ifndef __HAIKU__ 145 // work arround a BFS bug, attributes with the same name but different 146 // type fail to be written 147 node->RemoveAttr(attrName); 148#endif 149 150 // write buffer to attribute 151 ssize_t written = node->WriteAttr(attrName, B_VECTOR_ICON_TYPE, 0, 152 buffer.Buffer(), buffer.SizeUsed()); 153 if (written != (ssize_t)buffer.SizeUsed()) { 154 if (written < 0) { 155 printf("failed to write attribute: %s\n", 156 strerror((status_t)written)); 157 return (status_t)written; 158 } 159 printf("failed to write attribute\n"); 160 return B_ERROR; 161 } 162 163 return B_OK; 164} 165 166 167// #pragma mark - 168 169 170status_t 171FlatIconExporter::_Export(LittleEndianBuffer& buffer, const Icon* icon) 172{ 173 if (!buffer.Write(FLAT_ICON_MAGIC)) 174 return B_NO_MEMORY; 175 176#if PRINT_STATISTICS 177 fGradientSize = 0; 178 fGradientTransformSize = 0; 179 fPointCount = 0; 180#endif 181 182 // styles 183 const Container<Style>* styles = icon->Styles(); 184 status_t ret = _WriteStyles(buffer, styles); 185 if (ret < B_OK) 186 return ret; 187 188#if PRINT_STATISTICS 189 fStyleSectionSize = buffer.SizeUsed(); 190#endif 191 192 // paths 193 const Container<VectorPath>* paths = icon->Paths(); 194 ret = _WritePaths(buffer, paths); 195 if (ret < B_OK) 196 return ret; 197 198#if PRINT_STATISTICS 199 fPathSectionSize = buffer.SizeUsed() - fStyleSectionSize; 200#endif 201 202 // shapes 203 ret = _WriteShapes(buffer, styles, paths, icon->Shapes()); 204 if (ret < B_OK) 205 return ret; 206 207#if PRINT_STATISTICS 208 fShapeSectionSize = buffer.SizeUsed() 209 - (fStyleSectionSize + fPathSectionSize); 210#endif 211 212 return B_OK; 213} 214 215 216static bool 217_WriteTransformable(LittleEndianBuffer& buffer, const Transformable* transformable) 218{ 219 int32 matrixSize = Transformable::matrix_size; 220 double matrix[matrixSize]; 221 transformable->StoreTo(matrix); 222 for (int32 i = 0; i < matrixSize; i++) { 223// if (!buffer.Write((float)matrix[i])) 224// return false; 225 if (!write_float_24(buffer, (float)matrix[i])) 226 return false; 227 } 228 return true; 229} 230 231 232static bool 233_WriteTranslation(LittleEndianBuffer& buffer, const Transformable* transformable) 234{ 235 BPoint t(B_ORIGIN); 236 transformable->Transform(&t); 237 return write_coord(buffer, t.x) && write_coord(buffer, t.y); 238} 239 240 241status_t 242FlatIconExporter::_WriteStyles(LittleEndianBuffer& buffer, const Container<Style>* styles) 243{ 244 if (styles->CountItems() > 255) 245 return E_TOO_MANY_STYLES; 246 uint8 styleCount = min_c(255, styles->CountItems()); 247 if (!buffer.Write(styleCount)) 248 return B_NO_MEMORY; 249 250 for (int32 i = 0; i < styleCount; i++) { 251 Style* style = styles->ItemAtFast(i); 252 253 // style type 254 uint8 styleType; 255 256 const Gradient* gradient = style->Gradient(); 257 if (gradient) { 258 styleType = STYLE_TYPE_GRADIENT; 259 } else { 260 rgb_color color = style->Color(); 261 if (color.red == color.green && color.red == color.blue) { 262 // gray 263 if (style->Color().alpha == 255) 264 styleType = STYLE_TYPE_SOLID_GRAY_NO_ALPHA; 265 else 266 styleType = STYLE_TYPE_SOLID_GRAY; 267 } else { 268 // color 269 if (style->Color().alpha == 255) 270 styleType = STYLE_TYPE_SOLID_COLOR_NO_ALPHA; 271 else 272 styleType = STYLE_TYPE_SOLID_COLOR; 273 } 274 } 275 276 if (!buffer.Write(styleType)) 277 return B_NO_MEMORY; 278 279 if (styleType == STYLE_TYPE_SOLID_COLOR) { 280 // solid color 281 rgb_color color = style->Color(); 282 if (!buffer.Write(*(uint32*)&color)) 283 return B_NO_MEMORY; 284 } else if (styleType == STYLE_TYPE_SOLID_COLOR_NO_ALPHA) { 285 // solid color without alpha 286 rgb_color color = style->Color(); 287 if (!buffer.Write(color.red) 288 || !buffer.Write(color.green) 289 || !buffer.Write(color.blue)) 290 return B_NO_MEMORY; 291 } else if (styleType == STYLE_TYPE_SOLID_GRAY) { 292 // solid gray 293 rgb_color color = style->Color(); 294 if (!buffer.Write(color.red) 295 || !buffer.Write(color.alpha)) 296 return B_NO_MEMORY; 297 } else if (styleType == STYLE_TYPE_SOLID_GRAY_NO_ALPHA) { 298 // solid gray without alpha 299 if (!buffer.Write(style->Color().red)) 300 return B_NO_MEMORY; 301 } else if (styleType == STYLE_TYPE_GRADIENT) { 302 // gradient 303 if (!_WriteGradient(buffer, gradient)) 304 return B_NO_MEMORY; 305 } 306 } 307 308 return B_OK; 309} 310 311 312bool 313FlatIconExporter::_AnalysePath(VectorPath* path, uint8 pointCount, 314 int32& straightCount, int32& lineCount, int32& curveCount) 315{ 316 straightCount = 0; 317 lineCount = 0; 318 curveCount = 0; 319 320 BPoint lastPoint(B_ORIGIN); 321 for (uint32 p = 0; p < pointCount; p++) { 322 BPoint point; 323 BPoint pointIn; 324 BPoint pointOut; 325 if (!path->GetPointsAt(p, point, pointIn, pointOut)) 326 return B_ERROR; 327 if (point == pointIn && point == pointOut) { 328 if (point.x == lastPoint.x || point.y == lastPoint.y) 329 straightCount++; 330 else 331 lineCount++; 332 } else 333 curveCount++; 334 lastPoint = point; 335 336#if PRINT_STATISTICS 337 fUsedPoints.insert(point); 338 fUsedPoints.insert(pointIn); 339 fUsedPoints.insert(pointOut); 340 fPointCount += 3; 341#endif 342 } 343 344 return true; 345} 346 347 348 349static bool 350write_path_no_curves(LittleEndianBuffer& buffer, VectorPath* path, uint8 pointCount) 351{ 352//printf("write_path_no_curves()\n"); 353 for (uint32 p = 0; p < pointCount; p++) { 354 BPoint point; 355 if (!path->GetPointAt(p, point)) 356 return false; 357 if (!write_coord(buffer, point.x) || !write_coord(buffer, point.y)) 358 return false; 359 } 360 return true; 361} 362 363 364static bool 365write_path_curves(LittleEndianBuffer& buffer, VectorPath* path, uint8 pointCount) 366{ 367//printf("write_path_curves()\n"); 368 for (uint32 p = 0; p < pointCount; p++) { 369 BPoint point; 370 BPoint pointIn; 371 BPoint pointOut; 372 if (!path->GetPointsAt(p, point, pointIn, pointOut)) 373 return false; 374 if (!write_coord(buffer, point.x) 375 || !write_coord(buffer, point.y)) 376 return false; 377 if (!write_coord(buffer, pointIn.x) 378 || !write_coord(buffer, pointIn.y)) 379 return false; 380 if (!write_coord(buffer, pointOut.x) 381 || !write_coord(buffer, pointOut.y)) 382 return false; 383 } 384 return true; 385} 386 387 388static bool 389write_path_with_commands(LittleEndianBuffer& buffer, VectorPath* path, uint8 pointCount) 390{ 391 PathCommandQueue queue; 392 return queue.Write(buffer, path, pointCount); 393} 394 395 396 397status_t 398FlatIconExporter::_WritePaths(LittleEndianBuffer& buffer, const Container<VectorPath>* paths) 399{ 400 if (paths->CountItems() > 255) 401 return E_TOO_MANY_PATHS; 402 uint8 pathCount = min_c(255, paths->CountItems()); 403 if (!buffer.Write(pathCount)) 404 return B_NO_MEMORY; 405 406 for (uint32 i = 0; i < pathCount; i++) { 407 VectorPath* path = paths->ItemAtFast(i); 408 uint8 pathFlags = 0; 409 if (path->IsClosed()) 410 pathFlags |= PATH_FLAG_CLOSED; 411 412 if (path->CountPoints() > 255) 413 return E_PATH_TOO_MANY_POINTS; 414 uint8 pointCount = min_c(255, path->CountPoints()); 415 416 // see if writing segments with commands is more efficient 417 // than writing all segments as curves with no commands 418 int32 straightCount; 419 int32 lineCount; 420 int32 curveCount; 421 if (!_AnalysePath(path, pointCount, 422 straightCount, lineCount, curveCount)) 423 return B_ERROR; 424 425 int32 commandPathLength 426 = pointCount + straightCount * 2 + lineCount * 4 + curveCount * 12; 427 int32 plainPathLength 428 = pointCount * 12; 429//printf("segments: %d, command length: %ld, plain length: %ld\n", 430// pointCount, commandPathLength, plainPathLength); 431 432 if (commandPathLength < plainPathLength) { 433 if (curveCount == 0) 434 pathFlags |= PATH_FLAG_NO_CURVES; 435 else 436 pathFlags |= PATH_FLAG_USES_COMMANDS; 437 } 438 439 if (!buffer.Write(pathFlags) || !buffer.Write(pointCount)) 440 return B_NO_MEMORY; 441 442 if (pathFlags & PATH_FLAG_NO_CURVES) { 443 if (!write_path_no_curves(buffer, path, pointCount)) 444 return B_ERROR; 445 } else if (pathFlags & PATH_FLAG_USES_COMMANDS) { 446 if (!write_path_with_commands(buffer, path, pointCount)) 447 return B_ERROR; 448 } else { 449 if (!write_path_curves(buffer, path, pointCount)) 450 return B_ERROR; 451 } 452 } 453 454 return B_OK; 455} 456 457 458static bool 459_WriteTransformer(LittleEndianBuffer& buffer, Transformer* t) 460{ 461 if (AffineTransformer* affine 462 = dynamic_cast<AffineTransformer*>(t)) { 463 // affine 464 if (!buffer.Write((uint8)TRANSFORMER_TYPE_AFFINE)) 465 return false; 466 double matrix[6]; 467 affine->store_to(matrix); 468 for (int32 i = 0; i < 6; i++) { 469 if (!write_float_24(buffer, (float)matrix[i])) 470 return false; 471 } 472 473 } else if (ContourTransformer* contour 474 = dynamic_cast<ContourTransformer*>(t)) { 475 // contour 476 if (!buffer.Write((uint8)TRANSFORMER_TYPE_CONTOUR)) 477 return false; 478 uint8 width = (uint8)((int8)contour->width() + 128); 479 uint8 lineJoin = (uint8)contour->line_join(); 480 uint8 miterLimit = (uint8)contour->miter_limit(); 481 if (!buffer.Write(width) 482 || !buffer.Write(lineJoin) 483 || !buffer.Write(miterLimit)) 484 return false; 485 486 } else if (PerspectiveTransformer* perspective 487 = dynamic_cast<PerspectiveTransformer*>(t)) { 488 // perspective 489 if (!buffer.Write((uint8)TRANSFORMER_TYPE_PERSPECTIVE)) 490 return false; 491 double matrix[9]; 492 perspective->store_to(matrix); 493 for (int32 i = 0; i < 9; i++) { 494 if (!write_float_24(buffer, (float)matrix[i])) 495 return false; 496 } 497 498 } else if (StrokeTransformer* stroke 499 = dynamic_cast<StrokeTransformer*>(t)) { 500 // stroke 501 if (!buffer.Write((uint8)TRANSFORMER_TYPE_STROKE)) 502 return false; 503 uint8 width = (uint8)((int8)stroke->width() + 128); 504 uint8 lineOptions = (uint8)stroke->line_join(); 505 lineOptions |= ((uint8)stroke->line_cap()) << 4; 506 uint8 miterLimit = (uint8)stroke->miter_limit(); 507 508 if (!buffer.Write(width) 509 || !buffer.Write(lineOptions) 510 || !buffer.Write(miterLimit)) 511 return false; 512 } 513 514 return true; 515} 516 517 518static bool 519_WritePathSourceShape(LittleEndianBuffer& buffer, PathSourceShape* shape, 520 const Container<Style>* styles, const Container<VectorPath>* paths) 521{ 522 // find out which style this shape uses 523 Style* style = shape->Style(); 524 if (!style) 525 return false; 526 527 int32 styleIndex = styles->IndexOf(style); 528 if (styleIndex < 0 || styleIndex > 255) 529 return false; 530 531 if (shape->Paths()->CountItems() > 255) 532 return E_SHAPE_TOO_MANY_PATHS; 533 uint8 pathCount = min_c(255, shape->Paths()->CountItems()); 534 535 // write shape type and style index 536 if (!buffer.Write((uint8)SHAPE_TYPE_PATH_SOURCE) 537 || !buffer.Write((uint8)styleIndex) 538 || !buffer.Write(pathCount)) 539 return false; 540 541 // find out which paths this shape uses 542 for (uint32 i = 0; i < pathCount; i++) { 543 VectorPath* path = shape->Paths()->ItemAtFast(i); 544 int32 pathIndex = paths->IndexOf(path); 545 if (pathIndex < 0 || pathIndex > 255) 546 return false; 547 548 if (!buffer.Write((uint8)pathIndex)) 549 return false; 550 } 551 552 if (shape->Transformers()->CountItems() > 255) 553 return E_SHAPE_TOO_MANY_TRANSFORMERS; 554 uint8 transformerCount = min_c(255, shape->Transformers()->CountItems()); 555 556 // shape flags 557 uint8 shapeFlags = 0; 558 if (!shape->IsIdentity()) { 559 if (shape->IsTranslationOnly()) 560 shapeFlags |= SHAPE_FLAG_TRANSLATION; 561 else 562 shapeFlags |= SHAPE_FLAG_TRANSFORM; 563 } 564 if (shape->Hinting()) 565 shapeFlags |= SHAPE_FLAG_HINTING; 566 if (shape->MinVisibilityScale() != 0.0 567 || shape->MaxVisibilityScale() != 4.0) 568 shapeFlags |= SHAPE_FLAG_LOD_SCALE; 569 if (transformerCount > 0) 570 shapeFlags |= SHAPE_FLAG_HAS_TRANSFORMERS; 571 572 if (!buffer.Write((uint8)shapeFlags)) 573 return false; 574 575 if (shapeFlags & SHAPE_FLAG_TRANSFORM) { 576 // transformation 577 if (!_WriteTransformable(buffer, shape)) 578 return false; 579 } else if (shapeFlags & SHAPE_FLAG_TRANSLATION) { 580 // translation 581 if (!_WriteTranslation(buffer, shape)) 582 return false; 583 } 584 585 if (shapeFlags & SHAPE_FLAG_LOD_SCALE) { 586 // min max visibility scale 587 if (!buffer.Write( 588 (uint8)(shape->MinVisibilityScale() * 63.75 + 0.5)) 589 || !buffer.Write( 590 (uint8)(shape->MaxVisibilityScale() * 63.75 + 0.5))) { 591 return false; 592 } 593 } 594 595 if (shapeFlags & SHAPE_FLAG_HAS_TRANSFORMERS) { 596 // transformers 597 if (!buffer.Write(transformerCount)) 598 return false; 599 600 for (uint32 i = 0; i < transformerCount; i++) { 601 Transformer* transformer = shape->Transformers()->ItemAtFast(i); 602 if (!_WriteTransformer(buffer, transformer)) 603 return false; 604 } 605 } 606 607 return true; 608} 609 610 611status_t 612FlatIconExporter::_WriteShapes(LittleEndianBuffer& buffer, const Container<Style>* styles, 613 const Container<VectorPath>* paths, const Container<Shape>* shapes) 614{ 615 uint32 shapeCount = shapes->CountItems(); 616 617 // Count the number of exportable shapes 618 uint32 pathShapeCount = 0; 619 for (uint32 i = 0; i < shapeCount; i++) { 620 Shape* shape = shapes->ItemAtFast(i); 621 if (dynamic_cast<PathSourceShape*>(shape) != NULL) 622 pathShapeCount++; 623 } 624 625 // Write number of exportable shapes 626 if (pathShapeCount > 255) 627 return E_TOO_MANY_SHAPES; 628 if (!buffer.Write((uint8) pathShapeCount)) 629 return B_NO_MEMORY; 630 631 // Export each shape 632 for (uint32 i = 0; i < shapeCount; i++) { 633 Shape* shape = shapes->ItemAtFast(i); 634 635 PathSourceShape* pathSourceShape = dynamic_cast<PathSourceShape*>(shape); 636 if (pathSourceShape != NULL) { 637 if (!_WritePathSourceShape(buffer, pathSourceShape, styles, paths)) 638 return B_ERROR; 639 } 640 } 641 642 return B_OK; 643} 644 645 646bool 647FlatIconExporter::_WriteGradient(LittleEndianBuffer& buffer, const Gradient* gradient) 648{ 649#if PRINT_STATISTICS 650 size_t currentSize = buffer.SizeUsed(); 651#endif 652 653 uint8 gradientType = (uint8)gradient->Type(); 654 uint8 gradientFlags = 0; 655 uint8 gradientStopCount = (uint8)gradient->CountColors(); 656 657 // figure out flags 658 if (!gradient->IsIdentity()) 659 gradientFlags |= GRADIENT_FLAG_TRANSFORM; 660 661 bool alpha = false; 662 bool gray = true; 663 for (int32 i = 0; i < gradientStopCount; i++) { 664 BGradient::ColorStop* step = gradient->ColorAtFast(i); 665 if (step->color.alpha < 255) 666 alpha = true; 667 if (step->color.red != step->color.green 668 || step->color.red != step->color.blue) 669 gray = false; 670 } 671 if (!alpha) 672 gradientFlags |= GRADIENT_FLAG_NO_ALPHA; 673 if (gray) 674 gradientFlags |= GRADIENT_FLAG_GRAYS; 675 676 if (!buffer.Write(gradientType) 677 || !buffer.Write(gradientFlags) 678 || !buffer.Write(gradientStopCount)) 679 return false; 680 681 if (gradientFlags & GRADIENT_FLAG_TRANSFORM) { 682 if (!_WriteTransformable(buffer, gradient)) 683 return false; 684#if PRINT_STATISTICS 685 fGradientTransformSize += Transformable::matrix_size * sizeof(float); 686#endif 687 } 688 689 for (int32 i = 0; i < gradientStopCount; i++) { 690 BGradient::ColorStop* step = gradient->ColorAtFast(i); 691 uint8 stopOffset = (uint8)(step->offset * 255.0); 692 uint32 color = (uint32&)step->color; 693 if (!buffer.Write(stopOffset)) 694 return false; 695 if (alpha) { 696 if (gray) { 697 if (!buffer.Write(step->color.red) 698 || !buffer.Write(step->color.alpha)) 699 return false; 700 } else { 701 if (!buffer.Write(color)) 702 return false; 703 } 704 } else { 705 if (gray) { 706 if (!buffer.Write(step->color.red)) 707 return false; 708 } else { 709 if (!buffer.Write(step->color.red) 710 || !buffer.Write(step->color.green) 711 || !buffer.Write(step->color.blue)) 712 return false; 713 } 714 } 715 } 716 717#if PRINT_STATISTICS 718 fGradientSize += buffer.SizeUsed() - currentSize; 719#endif 720 721 return true; 722} 723 724