Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,35 @@
[![Tests](https://img.shields.io/github/actions/workflow/status/choowx/rasterize-svg/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/choowx/rasterize-svg/actions/workflows/run-tests.yml)
[![Total Downloads](https://img.shields.io/packagist/dt/choowx/rasterize-svg.svg?style=flat-square)](https://packagist.org/packages/choowx/rasterize-svg)

This package uses [Sharp](https://sharp.pixelplumbing.com/), a high-performance Node.js image processing library. Sharp is one of the fastest Node.js modules for converting and resizing images.

## Installation

You can install the package via composer:
### Prerequisites

Install Node.js (npm will be included automatically):
- Download and install from [nodejs.org](https://nodejs.org/)
- Choose the "LTS" (Long Term Support) version for stability
- After installation, verify everything is working by opening a terminal/command prompt and running:
```bash
node --version
npm --version
```
Both commands should display version numbers if the installation was successful.

### Package Installation
At the root of your PHP project:

1. Install the PHP package via composer:
```bash
composer require choowx/rasterize-svg
```

This package relies on the `sharp` js package being available on your system. In most cases you can accomplish this by issue this command in your project.

2. Install the required Node.js dependency:
```bash
npm install sharp
```
This will create a node_modules directory, package-lock.json and package.json

## Usage

Expand All @@ -29,6 +45,9 @@ $jpegBinaryString = Svg::make($svgString)->toJpeg();
$jpegBinaryString = Svg::make($svgString)->toJpg(); // Alias of toJpeg()
$pngBinaryString = Svg::make($svgString)->toPng();
$webpBinaryString = Svg::make($svgString)->toWebp();

// Resize the image to 100px wide and 100px high
$pngBinaryString = Svg::make($svgString, ['resize' => ['width' => 100, 'height' => 100]])->toPng();
```

If you want to straight away save the rasterized image on disk:
Expand Down
43 changes: 38 additions & 5 deletions bin/sharp.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,46 @@ const sharp = require('sharp');

(async () => {
const arguments = process.argv.slice(2);

const svgFilePath = arguments[0];
const format = arguments[1];
const operations = arguments[2] ? JSON.parse(arguments[2]) : null;

// Initialize sharp with input file
let image = sharp(svgFilePath);

// Apply format conversion
image = image.toFormat(format);

const result = await sharp(svgFilePath)
.toFormat(format)
.toBuffer()
// Apply image operations if provided
if (operations) {
for (const [operation, params] of Object.entries(operations)) {
switch (operation) {
case 'resize':
image = image.resize(params.width, params.height, params.options);
break;
case 'rotate':
image = image.rotate(params.angle, params.options);
break;
case 'flip':
image = image.flip();
break;
case 'flop':
image = image.flop();
break;
case 'blur':
image = image.blur(params.sigma);
break;
case 'sharpen':
image = image.sharpen(params.sigma, params.flat, params.jagged);
break;
case 'grayscale':
image = image.grayscale();
break;
// Add more operations as needed
}
}
}

process.stdout.write(result)
const result = await image.toBuffer();
process.stdout.write(result);
})();
9 changes: 9 additions & 0 deletions src/SharpSvgRasterizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ public function rasterize(): string
$this->format->value,
];

$options = $this->svg->getOptions();
if ($options) {
$command[] = json_encode($options);
}

$process = new Process(
command: $command,
cwd: __DIR__.'/../bin',
Expand All @@ -54,6 +59,10 @@ public function rasterize(): string

$this->cleanupTemporarySvgDirectory();

if ($process->getErrorOutput()) {
throw new \Exception($process->getErrorOutput());
}

return $process->getOutput();
}

Expand Down
19 changes: 16 additions & 3 deletions src/Svg.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,28 @@
class Svg
{
protected string $svg;
protected array $options = [];

public function __construct(string $svg)
public function __construct(string $svg, array $options = [])
{
$this->svg = $svg;
$this->options = $options;
}

public static function make(string $svg): self
public static function make(string $svg, array $options = []): self
{
return new static($svg);
return new static($svg, $options);
}

public function setOptions(array $options): self
{
$this->options = $options;
return $this;
}

public function getOptions(): array
{
return $this->options;
}

public function toString(): string
Expand Down
104 changes: 104 additions & 0 deletions tests/ImageOperationsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

namespace Choowx\RasterizeSvg\Tests;

use Choowx\RasterizeSvg\Svg;
use PHPUnit\Framework\TestCase;

class ImageOperationsTest extends TestCase
{
private string $tempDirectory;

protected function setUp(): void
{
parent::setUp();
$this->tempDirectory = sys_get_temp_dir();
}

protected function tearDown(): void
{
// Cleanup any remaining test files
foreach (['test-resize.png', 'test-resize-aspect.png'] as $file) {
$path = $this->tempDirectory . '/' . $file;
if (file_exists($path)) {
unlink($path);
}
}
parent::tearDown();
}

private function getTestSvg(): string
{
return '<svg width="200" height="200" viewBox="0 0 200 200">
<rect width="200" height="200" fill="blue"/>
</svg>';
}

public function testCanResizeImageToSpecificDimensions(): void
{
$resizeOptions = [
'resize' => [
'width' => 100,
'height' => 100,
'options' => [
'fit' => 'contain'
]
]
];

$pngData = Svg::make($this->getTestSvg(), $resizeOptions)->toPng();

$tempFile = $this->tempDirectory . '/test-resize.png';
file_put_contents($tempFile, $pngData);

$imageInfo = getimagesize($tempFile);

$this->assertEquals(100, $imageInfo[0]); // Width
$this->assertEquals(100, $imageInfo[1]); // Height
}

public function testCanResizeImageMaintainingAspectRatio(): void
{
$resizeOptions = [
'resize' => [
'width' => 100,
'height' => null,
'options' => [
'fit' => 'contain'
]
]
];

$pngData = Svg::make($this->getTestSvg(), $resizeOptions)->toPng();

$tempFile = $this->tempDirectory . '/test-resize-aspect.png';
file_put_contents($tempFile, $pngData);

$imageInfo = getimagesize($tempFile);

$this->assertEquals(100, $imageInfo[0]); // Width
$this->assertEquals(100, $imageInfo[1]); // Height maintains aspect ratio
}

public function testCanResizeImageWithFitCover(): void
{
$resizeOptions = [
'resize' => [
'width' => 100,
'height' => 50,
'options' => [
'fit' => 'cover'
]
]
];

$pngData = Svg::make($this->getTestSvg(), $resizeOptions)->toPng();
$tempFile = $this->tempDirectory . '/test-resize-cover.png';
file_put_contents($tempFile, $pngData);

$imageInfo = getimagesize($tempFile);

$this->assertEquals(100, $imageInfo[0]); // Width
$this->assertEquals(50, $imageInfo[1]); // Height
}
}