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

optimize batch generation of previews #19495

Merged
merged 3 commits into from
Apr 10, 2020
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
112 changes: 71 additions & 41 deletions lib/private/Preview/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,22 +91,37 @@ public function __construct(
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
*/
public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) {
$specification = [
'width' => $width,
'height' => $height,
'crop' => $crop,
'mode' => $mode,
];
$this->eventDispatcher->dispatch(
IPreview::EVENT,
new GenericEvent($file, $specification)
);

// since we only ask for one preview, and the generate method return the last one it created, it returns the one we want
return $this->generatePreviews($file, [$specification], $mimeType);
}

/**
* Generates previews of a file
*
* @param File $file
* @param array $specifications
* @param string $mimeType
* @return ISimpleFile the last preview that was generated
* @throws NotFoundException
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
*/
public function generatePreviews(File $file, array $specifications, $mimeType = null) {
//Make sure that we can read the file
if (!$file->isReadable()) {
throw new NotFoundException('Cannot read file');
}


$this->eventDispatcher->dispatch(
IPreview::EVENT,
new GenericEvent($file, [
'width' => $width,
'height' => $height,
'crop' => $crop,
'mode' => $mode
])
);

if ($mimeType === null) {
$mimeType = $file->getMimeType();
}
Expand All @@ -123,41 +138,57 @@ public function getPreview(File $file, $width = -1, $height = -1, $crop = false,

// Get the max preview and infer the max preview sizes from that
$maxPreview = $this->getMaxPreview($previewFolder, $file, $mimeType, $previewVersion);
$maxPreviewImage = null; // only load the image when we need it
if ($maxPreview->getSize() === 0) {
$maxPreview->delete();
throw new NotFoundException('Max preview size 0, invalid!');
}

list($maxWidth, $maxHeight) = $this->getPreviewSize($maxPreview, $previewVersion);
[$maxWidth, $maxHeight] = $this->getPreviewSize($maxPreview, $previewVersion);

// If both width and heigth are -1 we just want the max preview
if ($width === -1 && $height === -1) {
$width = $maxWidth;
$height = $maxHeight;
}
$preview = null;

// Calculate the preview size
list($width, $height) = $this->calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight);
foreach ($specifications as $specification) {
$width = $specification['width'] ?? -1;
$height = $specification['height'] ?? -1;
$crop = $specification['crop'] ?? false;
$mode = $specification['mode'] ?? IPreview::MODE_FILL;

// No need to generate a preview that is just the max preview
if ($width === $maxWidth && $height === $maxHeight) {
return $maxPreview;
}
// If both width and heigth are -1 we just want the max preview
if ($width === -1 && $height === -1) {
$width = $maxWidth;
$height = $maxHeight;
}

// Try to get a cached preview. Else generate (and store) one
try {
// Calculate the preview size
[$width, $height] = $this->calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight);

// No need to generate a preview that is just the max preview
if ($width === $maxWidth && $height === $maxHeight) {
// ensure correct return value if this was the last one
$preview = $maxPreview;
continue;
}

// Try to get a cached preview. Else generate (and store) one
try {
$preview = $this->getCachedPreview($previewFolder, $width, $height, $crop, $maxPreview->getMimeType(), $previewVersion);
} catch (NotFoundException $e) {
$preview = $this->generatePreview($previewFolder, $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion);
try {
$preview = $this->getCachedPreview($previewFolder, $width, $height, $crop, $maxPreview->getMimeType(), $previewVersion);
} catch (NotFoundException $e) {
if ($maxPreviewImage === null) {
$maxPreviewImage = $this->helper->getImage($maxPreview);
}

$preview = $this->generatePreview($previewFolder, $maxPreviewImage, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion);
}
} catch (\InvalidArgumentException $e) {
throw new NotFoundException("", 0, $e);
}
} catch (\InvalidArgumentException $e) {
throw new NotFoundException();
}

if ($preview->getSize() === 0) {
$preview->delete();
throw new NotFoundException('Cached preview size 0, invalid!');
if ($preview->getSize() === 0) {
$preview->delete();
throw new NotFoundException('Cached preview size 0, invalid!');
}
}

return $preview;
Expand Down Expand Up @@ -360,9 +391,8 @@ private function calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHei
* @throws NotFoundException
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
*/
private function generatePreview(ISimpleFolder $previewFolder, ISimpleFile $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight, $prefix) {
$preview = $this->helper->getImage($maxPreview);

private function generatePreview(ISimpleFolder $previewFolder, IImage $maxPreview, $width, $height, $crop, $maxWidth, $maxHeight, $prefix) {
$preview = $maxPreview;
if (!$preview->valid()) {
throw new \InvalidArgumentException('Failed to generate preview, failed to load image');
}
Expand All @@ -380,13 +410,13 @@ private function generatePreview(ISimpleFolder $previewFolder, ISimpleFile $maxP
$scaleH = $maxHeight / $widthR;
$scaleW = $width;
}
$preview->preciseResize((int)round($scaleW), (int)round($scaleH));
$preview = $preview->preciseResizeCopy((int)round($scaleW), (int)round($scaleH));
}
$cropX = (int)floor(abs($width - $preview->width()) * 0.5);
$cropY = (int)floor(abs($height - $preview->height()) * 0.5);
$preview->crop($cropX, $cropY, $width, $height);
$preview = $preview->cropCopy($cropX, $cropY, $width, $height);
} else {
$preview->resize(max($width, $height));
$preview = $maxPreview->resizeCopy(max($width, $height));
}


Expand Down Expand Up @@ -448,7 +478,7 @@ private function getExtention($mimeType) {
case 'image/gif':
return 'gif';
default:
throw new \InvalidArgumentException('Not a valid mimetype');
throw new \InvalidArgumentException('Not a valid mimetype: "' . $mimeType . '"');
}
}
}
44 changes: 31 additions & 13 deletions lib/private/PreviewManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,22 @@ public function hasProviders() {
return !empty($this->providers);
}

private function getGenerator(): Generator {
if ($this->generator === null) {
$this->generator = new Generator(
$this->config,
$this,
$this->appData,
new GeneratorHelper(
$this->rootFolder,
$this->config
),
$this->eventDispatcher
);
}
return $this->generator;
}

/**
* Returns a preview of a file
*
Expand All @@ -169,20 +185,22 @@ public function hasProviders() {
* @since 11.0.0 - \InvalidArgumentException was added in 12.0.0
*/
public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null) {
if ($this->generator === null) {
$this->generator = new Generator(
$this->config,
$this,
$this->appData,
new GeneratorHelper(
$this->rootFolder,
$this->config
),
$this->eventDispatcher
);
}
return $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType);
}

return $this->generator->getPreview($file, $width, $height, $crop, $mode, $mimeType);
/**
* Generates previews of a file
*
* @param File $file
* @param array $specifications
* @param string $mimeType
* @return ISimpleFile the last preview that was generated
* @throws NotFoundException
* @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
* @since 19.0.0
*/
public function generatePreviews(File $file, array $specifications, $mimeType = null) {
return $this->getGenerator()->generatePreviews($file, $specifications, $mimeType);
}

/**
Expand Down
102 changes: 94 additions & 8 deletions lib/private/legacy/OC_Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
*
*/

use OCP\IImage;

/**
* Class for basic image manipulation
*/
Expand Down Expand Up @@ -845,6 +847,17 @@ private function imagecreatefrombmp($fileName) {
* @return bool
*/
public function resize($maxSize) {
$result = $this->resizeNew($maxSize);
imagedestroy($this->resource);
$this->resource = $result;
return is_resource($result);
}

/**
* @param $maxSize
* @return resource | bool
*/
private function resizeNew($maxSize) {
if (!$this->valid()) {
$this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']);
return false;
Expand All @@ -861,8 +874,7 @@ public function resize($maxSize) {
$newHeight = $maxSize;
}

$this->preciseResize((int)round($newWidth), (int)round($newHeight));
return true;
return $this->preciseResizeNew((int)round($newWidth), (int)round($newHeight));
}

/**
Expand All @@ -871,6 +883,19 @@ public function resize($maxSize) {
* @return bool
*/
public function preciseResize(int $width, int $height): bool {
$result = $this->preciseResizeNew($width, $height);
imagedestroy($this->resource);
$this->resource = $result;
return is_resource($result);
}


/**
* @param int $width
* @param int $height
* @return resource | bool
*/
public function preciseResizeNew(int $width, int $height) {
if (!$this->valid()) {
$this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']);
return false;
Expand All @@ -896,9 +921,7 @@ public function preciseResize(int $width, int $height): bool {
imagedestroy($process);
return false;
}
imagedestroy($this->resource);
$this->resource = $process;
return true;
return $process;
}

/**
Expand Down Expand Up @@ -969,6 +992,22 @@ public function centerCrop($size = 0) {
* @return bool for success or failure
*/
public function crop(int $x, int $y, int $w, int $h): bool {
$result = $this->cropNew($x, $y, $w, $h);
imagedestroy($this->resource);
$this->resource = $result;
return is_resource($result);
}

/**
* Crops the image from point $x$y with dimension $wx$h.
*
* @param int $x Horizontal position
* @param int $y Vertical position
* @param int $w Width
* @param int $h Height
* @return resource | bool
*/
public function cropNew(int $x, int $y, int $w, int $h) {
if (!$this->valid()) {
$this->logger->error(__METHOD__ . '(): No image loaded', ['app' => 'core']);
return false;
Expand All @@ -993,9 +1032,7 @@ public function crop(int $x, int $y, int $w, int $h): bool {
imagedestroy($process);
return false;
}
imagedestroy($this->resource);
$this->resource = $process;
return true;
return $process;
}

/**
Expand Down Expand Up @@ -1045,6 +1082,55 @@ public function scaleDownToFit($maxWidth, $maxHeight) {
return false;
}

public function copy(): IImage {
$image = new OC_Image(null, $this->logger, $this->config);
$image->resource = imagecreatetruecolor($this->width(), $this->height());
imagecopy(
$image->resource(),
$this->resource(),
0,
0,
0,
0,
$this->width(),
$this->height()
);

return $image;
}

public function cropCopy(int $x, int $y, int $w, int $h): IImage {
$image = new OC_Image(null, $this->logger, $this->config);
$image->resource = $this->cropNew($x, $y, $w, $h);

return $image;
}

public function preciseResizeCopy(int $width, int $height): IImage {
$image = new OC_Image(null, $this->logger, $this->config);
$image->resource = $this->preciseResizeNew($width, $height);

return $image;
}

public function resizeCopy(int $maxSize): IImage {
$image = new OC_Image(null, $this->logger, $this->config);
$image->resource = $this->resizeNew($maxSize);

return $image;
}


/**
* Resizes the image preserving ratio, returning a new copy
*
* @param integer $maxSize The maximum size of either the width or height.
* @return bool
*/
public function copyResize($maxSize): IImage {

}

/**
* Destroys the current image and resets the object
*/
Expand Down
Loading