started work on *magick driver and updated tests

This commit is contained in:
Joby 2023-09-12 00:23:25 +00:00
parent 37ab60d81b
commit ac0e9e1acb
26 changed files with 654 additions and 69 deletions

14
.github/workflows/phpstan.yaml vendored Normal file
View file

@ -0,0 +1,14 @@
name: PHPStan
on: push
jobs:
phpstan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: php-actions/composer@v6
with:
dev: yes
- uses: php-actions/phpstan@v3
with:
path: src
level: max

13
.github/workflows/phpunit.yaml vendored Normal file
View file

@ -0,0 +1,13 @@
name: PHPUnit
on: push
jobs:
phpunit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: php-actions/composer@v6
with:
dev: yes
- uses: php-actions/phpunit@v3
with:
version: 10

3
.gitignore vendored
View file

@ -3,4 +3,5 @@ composer.phar
composer.lock
debug.log
/examples/out/*
!.gitkeep
!.gitkeep
.phpunit.result.cache

View file

@ -8,12 +8,11 @@ This library is under active development, and until a 1.0 release is made you sh
### Current progress
| Driver | Rotate | Mirror | Resize | Crop |
| :--------- | :----: | :----: | :----: | :--: |
| GD | X | X | X | X |
| Imagick | | | | |
| Gmagick | | | | |
| ImagickCLI | X | X | X | X |
| Driver | Rotate | Mirror | Resize | Crop |
| :-------------- | :----: | :----: | :----: | :---: |
| GDDriver | X | X | X | X |
| MagickDriver | | | | |
| MagickCliDriver | X | X | X | X |
## Roadmap
@ -22,9 +21,8 @@ This library is under active development, and until a 1.0 release is made you sh
A 1.0 release will not be made until the following drivers are available and solidly tested:
* GD
* Imagick
* Gmagick
* GmagickCLI
* Magick (unified driver that will use either ImageMagick or Gmagick automatically depending on what is available)
* MagickCliDriver (unified driver that can be configured to use either ImageMagick or Gmagick CLI tools)
### Transforms

View file

@ -24,14 +24,13 @@
},
"require-dev": {
"ext-gmagick": "*",
"ext-gd": "*"
"ext-gd": "*",
"phpunit/phpunit": "^10.3",
"phpstan/phpstan": "^1.10"
},
"autoload-dev": {
"psr-4": {
"ByJoby\\ImageTransform\\tests\\": "tests/"
}
},
"scripts": {
"test": "./vendor/bin/atoum -d tests"
}
}

4
phpstan.neon Normal file
View file

@ -0,0 +1,4 @@
parameters:
level: max
paths:
- src

31
phpunit.xml Normal file
View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
executionOrder="random"
failOnWarning="true"
failOnRisky="true"
failOnEmptyTestSuite="true"
beStrictAboutOutputDuringTests="true"
verbose="true"
bootstrap="vendor/autoload.php">
<php>
<ini name="display_errors" value="On" />
<ini name="error_reporting" value="-1" />
<ini name="xdebug.mode" value="coverage" />
</php>
<testsuites>
<testsuite name="Tests">
<directory>tests/</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory suffix=".php">src</directory>
</include>
<report>
<html outputDirectory="coverage" lowUpperBound="50" highLowerBound="90" />
</report>
</coverage>
</phpunit>

View file

@ -10,7 +10,8 @@ use ByJoby\ImageTransform\Drivers\GDDriver;
*/
class DefaultDriver
{
protected $driver;
/** @var DriverInterface|null */
protected static $driver;
public static function get(): DriverInterface
{

View file

@ -4,10 +4,4 @@ namespace ByJoby\ImageTransform\Drivers;
abstract class AbstractCliDriver extends AbstractDriver
{
public function __construct()
{
if (!function_exists('exec')) {
throw new \Exception("CLI drivers can't be used with the current configuration because exec is disabled");
}
}
}

View file

@ -5,20 +5,25 @@ namespace ByJoby\ImageTransform\Drivers;
use ByJoby\ImageTransform\DriverInterface;
use ByJoby\ImageTransform\Image;
use ByJoby\ImageTransform\Sizers\AbstractSizer;
use Exception;
abstract class AbstractDriver implements DriverInterface
{
/** @var string|null */
protected $tempDir = null;
/** @var int */
protected $chmod_dir = 0775;
/** @var int */
protected $chmod_file = 0665;
abstract protected function doSave(Image $image, string $filename);
abstract protected function doSave(Image $image, string $filename): void;
public function tempDir(): string
{
if (!$this->tempDir) {
$this->setTempDir(sys_get_temp_dir() . '/byjoby_image-transform/' . uniqid("", true));
}
// @phpstan-ignore-next-line this is actually checked
return $this->tempDir;
}
@ -31,7 +36,7 @@ abstract class AbstractDriver implements DriverInterface
return $this;
}
protected function mkdir(string $dir)
protected function mkdir(string $dir): bool
{
// return true if dir exists and is writeable
if (is_dir($dir) && is_writeable($dir)) {
@ -74,12 +79,15 @@ abstract class AbstractDriver implements DriverInterface
}
touch($filename);
}
$this->doSave($image, realpath($filename));
$filename = realpath($filename);
if (!$filename) throw new Exception("Invalid filename or path");
$this->doSave($image, $filename);
chmod($filename, $this->chmod_file);
return null;
} else {
$filename = $this->tempDir() . '/' . uniqid() . '.jpg';
$this->doSave($image, $filename);
/** @var string we can count on this being a string because we just wrote it */
$output = file_get_contents($filename);
unlink($filename);
return $output;

View file

@ -6,14 +6,20 @@ use ByJoby\ImageTransform\Image;
abstract class AbstractExtensionDriver extends AbstractDriver
{
// @phpstan-ignore-next-line we specify types in subclasses
abstract protected function getImageObject(Image $image);
// @phpstan-ignore-next-line we specify types in subclasses
abstract protected function doResize($object, Image $image);
// @phpstan-ignore-next-line we specify types in subclasses
abstract protected function doCrop($object, Image $image);
// @phpstan-ignore-next-line we specify types in subclasses
abstract protected function doFlip($object, Image $image);
// @phpstan-ignore-next-line we specify types in subclasses
abstract protected function doRotation($object, Image $image);
abstract protected function saveImageObject($object, string $filename);
// @phpstan-ignore-next-line we specify types in subclasses
abstract protected function saveImageObject($object, string $filename): void;
public function doSave(Image $image, string $filename)
public function doSave(Image $image, string $filename): void
{
$object = $this->getImageObject($image);
$object = $this->doRotation($object, $image);

View file

@ -3,6 +3,7 @@
namespace ByJoby\ImageTransform\Drivers;
use ByJoby\ImageTransform\Image;
use GdImage;
/**
* This driver uses PHP's built-in GD libary. This is by far the slowest driver,
@ -18,42 +19,62 @@ class GDDriver extends AbstractExtensionDriver
}
}
protected function getImageObject(Image $image)
protected function getImageObject(Image $image): GdImage
{
$source = $image->source();
$extension = strtolower(preg_replace('/^.+\./', '', $source));
/** @var string */
$extension = preg_replace('/^.+\./', '', $source);
$extension = strtolower($extension);
switch ($extension) {
case 'bmp':
// @phpstan-ignore-next-line this will throw an exception, which is good
return imagecreatefrombmp($source);
case 'gif':
// @phpstan-ignore-next-line this will throw an exception, which is good
return imagecreatefromgif($source);
case 'jpg':
// @phpstan-ignore-next-line this will throw an exception, which is good
return imagecreatefromjpeg($source);
case 'jpeg':
// @phpstan-ignore-next-line this will throw an exception, which is good
return imagecreatefromjpeg($source);
case 'png':
// @phpstan-ignore-next-line this will throw an exception, which is good
return imagecreatefrompng($source);
case 'wbmp':
// @phpstan-ignore-next-line this will throw an exception, which is good
return imagecreatefromwbmp($source);
case 'webp':
// @phpstan-ignore-next-line this will throw an exception, which is good
return imagecreatefromwebp($source);
case 'xbm':
// @phpstan-ignore-next-line this will throw an exception, which is good
return imagecreatefromxbm($source);
default:
throw new \Exception("Unsupported input type: " . htmlentities($source));
}
}
protected function doResize($object, Image $image)
/**
* @param GdImage $object
* @param Image $image
* @return GdImage
*/
protected function doResize($object, Image $image): GdImage
{
$sizer = $image->sizer();
if ($sizer->resizeToHeight() && $sizer->resizeToWidth()) {
// sizer is calling for a resize
/** @var GdImage */
$new = imagecreatetruecolor($sizer->resizeToWidth(), $sizer->resizeToHeight());
imagecopyresampled(
$new, $object,
0, 0,//dst x/y
0, 0,
$new,
$object,
0,
0,
//dst x/y
0,
0,
$sizer->resizeToWidth(), $sizer->resizeToHeight(),
$sizer->originalWidth(), $sizer->originalHeight()
);
@ -64,17 +85,26 @@ class GDDriver extends AbstractExtensionDriver
}
}
protected function doCrop($object, Image $image)
/**
* @param GdImage $object
* @param Image $image
* @return GdImage
*/
protected function doCrop($object, Image $image): GdImage
{
$sizer = $image->sizer();
if ($sizer->cropToHeight() && $sizer->cropToWidth()) {
// sizer is calling for a crop
/** @var GdImage */
$new = imagecreatetruecolor($sizer->cropToWidth(), $sizer->cropToHeight());
imagecopyresampled(
$new, $object,
($sizer->cropToWidth()-$sizer->resizeToWidth())/2,($sizer->cropToHeight()-$sizer->resizeToHeight())/2,
0,0,
$sizer->resizetoWidth(),$sizer->resizeToHeight(),$sizer->resizeToWidth(),$sizer->resizeToHeight()
$new,
$object,
($sizer->cropToWidth() - $sizer->resizeToWidth()) / 2, ($sizer->cropToHeight() - $sizer->resizeToHeight()) / 2,
0,
0,
// @phpstan-ignore-next-line these are definitely set
$sizer->resizetoWidth(), $sizer->resizeToHeight(), $sizer->resizeToWidth(), $sizer->resizeToHeight()
);
return $new;
} else {
@ -83,47 +113,65 @@ class GDDriver extends AbstractExtensionDriver
}
}
protected function doFlip($object, Image $image)
/**
* @param GdImage $object
* @param Image $image
* @return GdImage
*/
protected function doFlip($object, Image $image): GdImage
{
if ($image->getFlipH()) {
imageflip($object,IMG_FLIP_HORIZONTAL);
imageflip($object, IMG_FLIP_HORIZONTAL);
}
if ($image->getFlipV()) {
imageflip($object,IMG_FLIP_VERTICAL);
imageflip($object, IMG_FLIP_VERTICAL);
}
return $object;
}
protected function doRotation($object, Image $image)
/**
* @param GdImage $object
* @param Image $image
* @return GdImage
*/
protected function doRotation($object, Image $image): GdImage
{
if ($rotationAmount = 360 - $image->rotation() * 90) {
// @phpstan-ignore-next-line
return imagerotate($object, $rotationAmount, 0);
}
return $object;
}
protected function saveImageObject($object, string $filename)
/**
* @param GdImage $object
* @param string $filename
* @return void
*/
protected function saveImageObject($object, string $filename): void
{
$extension = strtolower(preg_replace('/^.+\./', '', $filename));
/** @var string */
$extension = preg_replace('/^.+\./', '', $filename);
$extension = strtolower($extension);
switch ($extension) {
case 'bmp':
return imagebmp($object, $filename);
imagebmp($object, $filename);
case 'gif':
return imagegif($object, $filename);
imagegif($object, $filename);
case 'jpg':
return imagejpeg($object, $filename);
imagejpeg($object, $filename);
case 'jpeg':
return imagejpeg($object, $filename);
imagejpeg($object, $filename);
case 'png':
return imagepng($object, $filename);
imagepng($object, $filename);
case 'wbmp':
return imagewbmp($object, $filename);
imagewbmp($object, $filename);
case 'webp':
return imagewebp($object, $filename);
imagewebp($object, $filename);
case 'xbm':
return imagexbm($object, $filename);
imagexbm($object, $filename);
default:
throw new \Exception("Unsupported output type: " . htmlentities($filename));
}
}
}
}

View file

@ -13,12 +13,8 @@ use ByJoby\ImageTransform\Image;
*/
class MagickCliDriver extends AbstractCliDriver
{
protected $mogrify_executable;
public function __construct($mogrify_executable = 'magick mogrify')
public function __construct(protected string $mogrify_executable = 'magick mogrify')
{
parent::__construct();
$this->mogrify_executable = $mogrify_executable;
}
protected function mogrifyExecutable(): string
@ -26,7 +22,7 @@ class MagickCliDriver extends AbstractCliDriver
return $this->mogrify_executable;
}
protected function doSave(Image $image, string $filename)
protected function doSave(Image $image, string $filename): void
{
// basics of command
$command = [

View file

@ -0,0 +1,144 @@
<?php
/* image-transform | https://github.com/jobyone/image-transform | MIT License */
namespace ByJoby\ImageTransform\Drivers;
use ByJoby\ImageTransform\Image;
use Gmagick;
use GmagickPixel;
use Imagick;
use ImagickPixel;
/**
* This driver uses either ImageMagick or Gmagick, depending on its constructor
* argument.
*/
class MagickDriver extends AbstractExtensionDriver
{
/**
* By default this driver will use Imagick, but if you pass a true value it
* will instead use Gmagick.
*
* @param boolean $use_gmagick
*/
public function __construct(protected bool $use_gmagick = false)
{
}
protected function getImageObject(Image $image): Gmagick|Imagick
{
if ($this->use_gmagick) return new Gmagick($image->source());
else return new Imagick($image->source());
}
/**
* @param Gmagick|Imagick $object
* @param Image $image
* @return Gmagick|Imagick
*/
protected function doResize($object, Image $image): Gmagick|Imagick
{
$sizer = $image->sizer();
if ($sizer->resizeToHeight() && $sizer->resizeToWidth()) {
if ($object instanceof Gmagick) {
$object->resizeimage(
$sizer->resizeToWidth(),
$sizer->resizeToHeight(),
Gmagick::FILTER_LANCZOS,
0
);
} else {
$object->resizeImage(
$sizer->resizeToWidth(),
$sizer->resizeToHeight(),
Imagick::FILTER_LANCZOS,
0
);
}
}
return $object;
}
/**
* @param Gmagick|Imagick $object
* @param Image $image
* @return Gmagick|Imagick
*/
protected function doCrop($object, Image $image): Gmagick|Imagick
{
$sizer = $image->sizer();
$height = $sizer->cropToHeight();
$width = $sizer->cropToWidth();
if ($height && $width) {
$x = intval(($sizer->resizetoWidth() - $width) / 2);
$y = intval(($sizer->resizetoHeight() - $height) / 2);
if ($object instanceof Gmagick) {
$object->cropimage(
$width,
$height,
$x,
$y
);
} else {
$object->cropImage(
$width,
$height,
$x,
$y
);
}
}
return $object;
}
/**
* @param Gmagick|Imagick $object
* @param Image $image
* @return Gmagick|Imagick
*/
protected function doFlip($object, Image $image): Gmagick|Imagick
{
if ($image->getFlipH()) {
if ($object instanceof Gmagick) $object->flipimage();
else $object->flipImage();
}
if ($image->getFlipV()) {
if ($object instanceof Gmagick) $object->flopimage();
else $object->flopImage();
}
return $object;
}
/**
* @param Gmagick|Imagick $object
* @param Image $image
* @return Gmagick|Imagick
*/
protected function doRotation($object, Image $image): Gmagick|Imagick
{
if ($image->rotation()) {
if ($object instanceof Gmagick) $object->rotateimage(new GmagickPixel("#000"), $image->rotation() * 90);
else $object->rotateImage(new ImagickPixel("#000"), $image->rotation() * 90);
}
return $object;
}
/**
* @param Gmagick|Imagick $object
* @param string $filename
* @return void
*/
protected function saveImageObject($object, string $filename): void
{
/** @var string */
$format = preg_replace('/^.+\./', '', $filename);
$format = strtoupper($format);
if ($format == 'JPG') $format = 'JPEG';
if ($object instanceof Gmagick) {
$object->setimageformat($format);
$object->writeimage($filename, true);
} else {
$object->setImageFormat($format);
$object->writeImage($filename);
}
}
}

View file

@ -7,13 +7,21 @@ use ByJoby\ImageTransform\Sizers\Original;
class Image
{
/** @var string */
protected $source;
/** @var DriverInterface|null */
protected $driver;
/** @var int */
protected $originalWidth;
/** @var int */
protected $originalHeight;
/** @var int */
protected $rotation = 0;
/** @var boolean */
protected $flipH = false;
/** @var boolean */
protected $flipV = false;
/** @var AbstractSizer */
protected $sizer = null;
public function __construct(string $source, AbstractSizer|null $sizer = null)
@ -30,10 +38,11 @@ class Image
public function setSource(string $source): static
{
// set source
$this->source = realpath($source);
if (!$this->source) {
throw new \Exception("Source image not found: " . htmlentities($source));
$source = realpath($source);
if (!$source) {
throw new \Exception("Source image not found");
}
$this->source = $source;
// validate file
if (!is_file($this->source)) {
throw new \Exception("Image file doesn't exist: " . htmlentities($this->source));
@ -42,7 +51,11 @@ class Image
throw new \Exception("Invalid image file: " . htmlentities($this->source));
}
// get height/width
list($this->originalWidth, $this->originalHeight) = getimagesize($this->source);
$size = getimagesize($this->source);
if (!$size) {
throw new \Exception("Couldn't get image size: " . htmlentities($this->source));
}
list($this->originalWidth, $this->originalHeight) = $size;
// return self
return $this;
}
@ -55,7 +68,7 @@ class Image
public function setSizer(AbstractSizer $sizer): static
{
$this->sizer = clone $sizer;
$this->sizer->image($this);
$this->sizer->setImage($this);
return $this;
}

View file

@ -6,6 +6,7 @@ use ByJoby\ImageTransform\Image;
abstract class AbstractSizer
{
/** @var Image */
protected $image;
abstract public function width(): int;
@ -46,8 +47,9 @@ abstract class AbstractSizer
return $this->originalWidth()/$this->originalHeight();
}
public function image(Image $image)
public function setImage(Image $image): static
{
$this->image = $image;
return $this;
}
}

View file

@ -4,6 +4,7 @@ namespace ByJoby\ImageTransform\Sizers;
class Cover extends AbstractSizer
{
/** @var int */
protected $width, $height;
public function __construct(int $width, int $height)
@ -17,14 +18,17 @@ class Cover extends AbstractSizer
return $this->width / $this->height;
}
/**
* @return array{height:int,width:int}
*/
protected function calculateSize(): array
{
if ($this->targetRatio() < $this->originalRatio()) {
$height = $this->height;
$width = round($height * $this->originalRatio());
$width = intval(round($height * $this->originalRatio()));
} else {
$width = $this->width;
$height = round($width / $this->originalRatio());
$height = intval(round($width / $this->originalRatio()));
}
return [
'height' => $height,
@ -61,4 +65,4 @@ class Cover extends AbstractSizer
{
return $this->height;
}
}
}

View file

@ -4,6 +4,7 @@ namespace ByJoby\ImageTransform\Sizers;
class Fit extends AbstractSizer
{
/** @var int */
protected $width, $height;
public function __construct(int $width, int $height)
@ -27,14 +28,17 @@ class Fit extends AbstractSizer
return $this->width / $this->height;
}
/**
* @return array{height:int,width:int}
*/
protected function calculateSize(): array
{
if ($this->targetRatio() > $this->originalRatio()) {
$height = $this->height;
$width = round($height * $this->originalRatio());
$width = intval(round($height * $this->originalRatio()));
} else {
$width = $this->width;
$height = round($width / $this->originalRatio());
$height = intval(round($width / $this->originalRatio()));
}
return [
'height' => $height,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

109
tests/Sizers/CoverTest.php Normal file
View file

@ -0,0 +1,109 @@
<?php
/* image-transform | https://github.com/jobyone/image-transform | MIT License */
namespace ByJoby\ImageTransform\Sizers;
use ByJoby\ImageTransform\Image;
use PHPUnit\Framework\TestCase;
class CoverTest extends TestCase
{
public function testSquareImageSizedSquare(): void
{
// mock 200x200 image
$image = $this->createMock(Image::class);
$image->method("originalWidth")->willReturn(200);
$image->method("originalHeight")->willReturn(200);
// spin up a 50x50 sizer
$sizer = new Cover(50, 50);
$sizer->setImage($image);
// resized to cover
$this->assertEquals(50, $sizer->resizeToWidth());
$this->assertEquals(50, $sizer->resizeToHeight());
// cropped to size
$this->assertEquals(50, $sizer->cropToWidth());
$this->assertEquals(50, $sizer->cropToHeight());
}
public function testSquareImageSizedLandscape(): void
{
// mock 200x200 image
$image = $this->createMock(Image::class);
$image->method("originalWidth")->willReturn(200);
$image->method("originalHeight")->willReturn(200);
// spin up a 100x50 sizer
$sizer = new Cover(100, 50);
$sizer->setImage($image);
// resized to cover
$this->assertEquals(100, $sizer->resizeToWidth());
$this->assertEquals(100, $sizer->resizeToHeight());
// cropped to size
$this->assertEquals(100, $sizer->cropToWidth());
$this->assertEquals(50, $sizer->cropToHeight());
}
public function testSquareImageSizedPortrait(): void
{
// mock 200x200 image
$image = $this->createMock(Image::class);
$image->method("originalWidth")->willReturn(200);
$image->method("originalHeight")->willReturn(200);
// spin up a 50x100 sizer
$sizer = new Cover(50, 100);
$sizer->setImage($image);
// resized to cover
$this->assertEquals(100, $sizer->resizeToWidth());
$this->assertEquals(100, $sizer->resizeToHeight());
// cropped to size
$this->assertEquals(50, $sizer->cropToWidth());
$this->assertEquals(100, $sizer->cropToHeight());
}
public function testLandscapeImageSizedSquare(): void
{
// mock 200x100 image
$image = $this->createMock(Image::class);
$image->method("originalWidth")->willReturn(200);
$image->method("originalHeight")->willReturn(100);
// spin up a 50x50 sizer
$sizer = new Cover(50, 50);
$sizer->setImage($image);
// resized to cover
$this->assertEquals(100, $sizer->resizeToWidth());
$this->assertEquals(50, $sizer->resizeToHeight());
// cropped to size
$this->assertEquals(50, $sizer->cropToWidth());
$this->assertEquals(50, $sizer->cropToHeight());
}
public function testLandscapeImageSizedLandscape(): void
{
// mock 200x100 image
$image = $this->createMock(Image::class);
$image->method("originalWidth")->willReturn(200);
$image->method("originalHeight")->willReturn(100);
// spin up a 100x50 sizer
$sizer = new Cover(100, 50);
$sizer->setImage($image);
// resized to cover
$this->assertEquals(100, $sizer->resizeToWidth());
$this->assertEquals(50, $sizer->resizeToHeight());
// cropped to size
$this->assertEquals(100, $sizer->cropToWidth());
$this->assertEquals(50, $sizer->cropToHeight());
}
public function testLandscapeImageSizedPortrait(): void
{
// mock 200x100 image
$image = $this->createMock(Image::class);
$image->method("originalWidth")->willReturn(200);
$image->method("originalHeight")->willReturn(100);
// spin up a 50x100 sizer
$sizer = new Cover(50, 100);
$sizer->setImage($image);
// resized to cover
$this->assertEquals(200, $sizer->resizeToWidth());
$this->assertEquals(100, $sizer->resizeToHeight());
// cropped to size
$this->assertEquals(50, $sizer->cropToWidth());
$this->assertEquals(100, $sizer->cropToHeight());
}
}

BIN
tests/input-100x200.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

BIN
tests/input-200x100.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

BIN
tests/input-200x200.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

196
tests/test.svg Normal file
View file

@ -0,0 +1,196 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="200"
height="200"
viewBox="0 0 200 200"
version="1.1"
id="svg1"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
sodipodi:docname="test.svg"
inkscape:export-filename="input-200x200.png.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="false"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showguides="true"
inkscape:zoom="2.3267609"
inkscape:cx="94.552044"
inkscape:cy="68.980014"
inkscape:window-width="1736"
inkscape:window-height="1023"
inkscape:window-x="156"
inkscape:window-y="153"
inkscape:window-maximized="0"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g3">
<rect
style="fill:#0000ff;stroke-width:0.188982"
id="rect1"
width="28.571428"
height="25"
x="0"
y="-150"
transform="scale(1,-1)" />
<rect
style="fill:#000000;stroke-width:0.188982"
id="rect1-6"
width="28.571428"
height="25"
x="28.571428"
y="-150"
transform="scale(1,-1)" />
<rect
style="fill:#ff00ff;stroke-width:0.188982"
id="rect1-61"
width="28.571428"
height="25"
x="57.142857"
y="-150"
transform="scale(1,-1)" />
<rect
style="fill:#000000;stroke-width:0.188982"
id="rect1-6-8"
width="28.571428"
height="25"
x="85.714287"
y="-150"
transform="scale(1,-1)" />
<rect
style="fill:#00ffff;stroke-width:0.188982"
id="rect1-61-0"
width="28.571428"
height="25"
x="114.28571"
y="-150"
transform="scale(1,-1)" />
<rect
style="fill:#000000;stroke-width:0.188982"
id="rect1-6-8-2"
width="28.571428"
height="25"
x="142.85715"
y="-150"
transform="scale(1,-1)" />
<rect
style="fill:#000080;stroke-width:0.447214"
id="rect2"
width="40"
height="50"
x="0"
y="150" />
<rect
style="fill:#f2f2f2;stroke-width:0.632456"
id="rect3"
width="40"
height="50"
x="40"
y="150" />
<rect
style="fill:#cccccc;stroke-width:0.188982"
id="rect1-61-0-9"
width="28.571428"
height="25"
x="171.42857"
y="-150"
transform="scale(1,-1)" />
<rect
style="fill:#cccccc;stroke-width:0.422577"
id="rect1-1"
width="28.571428"
height="125"
x="0"
y="-125"
transform="scale(1,-1)" />
<rect
style="fill:#808000;stroke-width:0.422577"
id="rect1-6-2"
width="28.571428"
height="125"
x="28.571428"
y="-125"
transform="scale(1,-1)" />
<rect
style="fill:#008080;stroke-width:0.422577"
id="rect1-61-9"
width="28.571428"
height="125"
x="57.142857"
y="-125"
transform="scale(1,-1)" />
<rect
style="fill:#008000;stroke-width:0.422577"
id="rect1-6-8-3"
width="28.571428"
height="125"
x="85.714287"
y="-125"
transform="scale(1,-1)" />
<rect
style="fill:#800080;stroke-width:0.422577"
id="rect1-61-0-1"
width="28.571428"
height="125"
x="114.28571"
y="-125"
transform="scale(1,-1)" />
<rect
style="fill:#800000;stroke-width:0.422577"
id="rect1-6-8-2-9"
width="28.571428"
height="125"
x="142.85715"
y="-125"
transform="scale(1,-1)" />
<rect
style="fill:#000080;stroke-width:0.422577"
id="rect1-61-0-9-4"
width="28.571428"
height="125"
x="171.42857"
y="-125"
transform="scale(1,-1)" />
<rect
style="fill:#800080;stroke-width:0.632456"
id="rect3-5"
width="40"
height="50"
x="80"
y="150" />
<rect
style="fill:#4d4d4d;stroke-width:0.632456"
id="rect3-5-2"
width="40"
height="50"
x="120"
y="150" />
<rect
style="fill:#1a1a1a;stroke-width:0.632456"
id="rect3-5-2-7"
width="40"
height="50"
x="160"
y="150" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB