Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature - Create In-Memory Images from strings or streams #3157

Merged
merged 5 commits into from
Nov 6, 2022
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).

### Added

- Allow the creation of In-Memory Drawings from a string of binary image data, or from a stream. [PR #3157](https://github.com/PHPOffice/PhpSpreadsheet/pull/3157)
- Xlsx Reader support for Pivot Tables [PR #2829](https://github.com/PHPOffice/PhpSpreadsheet/pull/2829)

### Changed
Expand Down
16 changes: 16 additions & 0 deletions docs/topics/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,22 @@ $drawing->setHeight(36);
$drawing->setWorksheet($spreadsheet->getActiveSheet());
```

Note that GD images are memory-intensive.

### Creating a Drawing from string or stream data

If you want to create a drawing from a string containing the binary image data, or from an external datasource such as an S3 bucket, then you can create a new MemoryDrawing from these sources using the `fromString()` or `fromStream()` static methods.

```php
$drawing = MemoryDrawing::fromString($imageString);
```

```php
$drawing = MemoryDrawing::fromStream($imageStreamFromS3Bucket);
```

Note that this is a memory-intensive process, like all gd images; and also creates a temporary file.

## Reading Images from a worksheet

A commonly asked question is how to retrieve the images from a workbook
Expand Down
121 changes: 121 additions & 0 deletions src/PhpSpreadsheet/Worksheet/MemoryDrawing.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use GdImage;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Shared\File;

class MemoryDrawing extends BaseDrawing
{
Expand All @@ -19,6 +20,12 @@ class MemoryDrawing extends BaseDrawing
const MIMETYPE_GIF = 'image/gif';
const MIMETYPE_JPEG = 'image/jpeg';

const SUPPORTED_MIME_TYPES = [
self::MIMETYPE_GIF,
self::MIMETYPE_JPEG,
self::MIMETYPE_PNG,
];

/**
* Image resource.
*
Expand Down Expand Up @@ -127,6 +134,120 @@ private function cloneResource(): void
$this->imageResource = $clone;
}

/**
* @param resource $imageStream Stream data to be converted to a Memory Drawing
*
* @throws Exception
*/
public static function fromStream($imageStream): self
{
$streamValue = stream_get_contents($imageStream);
if ($streamValue === false) {
throw new Exception('Unable to read data from stream');
}

return self::fromString($streamValue);
}

/**
* @param string $imageString String data to be converted to a Memory Drawing
*
* @throws Exception
*/
public static function fromString(string $imageString): self
{
$gdImage = @imagecreatefromstring($imageString);
if ($gdImage === false) {
throw new Exception('Value cannot be converted to an image');
}

$mimeType = self::identifyMimeType($imageString);
$renderingFunction = self::identifyRenderingFunction($mimeType);

$drawing = new self();
$drawing->setImageResource($gdImage);
$drawing->setRenderingFunction($renderingFunction);
$drawing->setMimeType($mimeType);

return $drawing;
}

private static function identifyRenderingFunction(string $mimeType): string
{
switch ($mimeType) {
case self::MIMETYPE_PNG:
return self::RENDERING_PNG;
case self::MIMETYPE_JPEG:
return self::RENDERING_JPEG;
case self::MIMETYPE_GIF:
return self::RENDERING_GIF;
}

return self::RENDERING_DEFAULT;
}

/**
* @throws Exception
*/
private static function identifyMimeType(string $imageString): string
{
$temporaryFileName = File::temporaryFilename();
file_put_contents($temporaryFileName, $imageString);

$mimeType = self::identifyMimeTypeUsingExif($temporaryFileName);
if ($mimeType !== null) {
unlink($temporaryFileName);

return $mimeType;
}

$mimeType = self::identifyMimeTypeUsingGd($temporaryFileName);
if ($mimeType !== null) {
unlink($temporaryFileName);

return $mimeType;
}

unlink($temporaryFileName);

return self::MIMETYPE_DEFAULT;
}

private static function identifyMimeTypeUsingExif(string $temporaryFileName): ?string
{
if (function_exists('exif_imagetype')) {
$imageType = @exif_imagetype($temporaryFileName);
$mimeType = ($imageType) ? image_type_to_mime_type($imageType) : null;

return self::supportedMimeTypes($mimeType);
}

return null;
}

private static function identifyMimeTypeUsingGd(string $temporaryFileName): ?string
{
if (function_exists('getimagesize')) {
$imageSize = @getimagesize($temporaryFileName);
if (is_array($imageSize)) {
$mimeType = $imageSize['mime'] ?? null;

return self::supportedMimeTypes($mimeType);
}
}

return null;
}

private static function supportedMimeTypes(?string $mimeType = null): ?string
{
if (in_array($mimeType, self::SUPPORTED_MIME_TYPES, true)) {
return $mimeType;
}

return null;
}

/**
* Get image resource.
*
Expand Down
96 changes: 96 additions & 0 deletions tests/PhpSpreadsheetTests/Worksheet/MemoryDrawingTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

namespace PhpOffice\PhpSpreadsheetTests\Worksheet;

use GdImage;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
use PHPUnit\Framework\TestCase;

class MemoryDrawingTest extends TestCase
{
public function testMemoryDrawing(): void
{
$name = 'In-Memory image';
$gdImage = @imagecreatetruecolor(120, 20);
if ($gdImage === false) {
self::markTestSkipped('Unable to create GD Image for MemoryDrawing');
}

$textColor = (int) imagecolorallocate($gdImage, 255, 255, 255);
imagestring($gdImage, 1, 5, 5, 'Created with PhpSpreadsheet', $textColor);

$drawing = new MemoryDrawing();
$drawing->setName($name);
$drawing->setDescription('In-Memory image 1');
$drawing->setCoordinates('A1');
$drawing->setImageResource($gdImage);
$drawing->setRenderingFunction(MemoryDrawing::RENDERING_PNG);
$drawing->setMimeType(MemoryDrawing::MIMETYPE_PNG);

if (version_compare(PHP_VERSION, '8.0.0', '>=') === true) {
self::assertIsObject($drawing->getImageResource());
/** @phpstan-ignore-next-line */
self::assertInstanceOf(GdImage::class, $drawing->getImageResource());
} else {
self::assertIsResource($drawing->getImageResource());
}

self::assertSame(MemoryDrawing::MIMETYPE_DEFAULT, $drawing->getMimeType());
self::assertSame(MemoryDrawing::RENDERING_DEFAULT, $drawing->getRenderingFunction());
}

public function testMemoryDrawingFromString(): void
{
$imageFile = __DIR__ . '/../../data/Worksheet/officelogo.jpg';

$imageString = file_get_contents($imageFile);
if ($imageString === false) {
self::markTestSkipped('Unable to read Image file for MemoryDrawing');
}
$drawing = MemoryDrawing::fromString($imageString);

if (version_compare(PHP_VERSION, '8.0.0', '>=') === true) {
self::assertIsObject($drawing->getImageResource());
/** @phpstan-ignore-next-line */
self::assertInstanceOf(GdImage::class, $drawing->getImageResource());
} else {
self::assertIsResource($drawing->getImageResource());
}

self::assertSame(MemoryDrawing::MIMETYPE_JPEG, $drawing->getMimeType());
self::assertSame(MemoryDrawing::RENDERING_JPEG, $drawing->getRenderingFunction());
}

public function testMemoryDrawingFromInvalidString(): void
{
$this->expectException(Exception::class);
$this->expectExceptionMessage('Value cannot be converted to an image');

$imageString = 'I am not an image';
MemoryDrawing::fromString($imageString);
}

public function testMemoryDrawingFromStream(): void
{
$imageFile = __DIR__ . '/../../data/Worksheet/officelogo.jpg';

$imageStream = fopen($imageFile, 'rb');
if ($imageStream === false) {
self::markTestSkipped('Unable to read Image file for MemoryDrawing');
}
$drawing = MemoryDrawing::fromStream($imageStream);
fclose($imageStream);

if (version_compare(PHP_VERSION, '8.0.0', '>=') === true) {
self::assertIsObject($drawing->getImageResource());
/** @phpstan-ignore-next-line */
self::assertInstanceOf(GdImage::class, $drawing->getImageResource());
} else {
self::assertIsResource($drawing->getImageResource());
}

self::assertSame(MemoryDrawing::MIMETYPE_JPEG, $drawing->getMimeType());
self::assertSame(MemoryDrawing::RENDERING_JPEG, $drawing->getRenderingFunction());
}
}
Binary file added tests/data/Worksheet/officelogo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.