diff --git a/Web/Models/Entities/Photo.php b/Web/Models/Entities/Photo.php index 8862f8a1..3de9fae0 100644 --- a/Web/Models/Entities/Photo.php +++ b/Web/Models/Entities/Photo.php @@ -1,5 +1,6 @@ getWidth() / $image->getHeight()) > ($px / $py)) { + # For some weird reason using resize with EXACT flag causes system to consume an unholy amount of RAM + $image->crop(0, 0, "100%", (int) ceil(($px * $image->getWidth()) / $py)); + } + } + + if(isset($size["maxSize"])) { + $maxSize = (int) $size["maxSize"]; + $image->resize($maxSize, $maxSize, Image::SHRINK_ONLY | Image::FIT); + } else if(isset($size["maxResolution"])) { + $resolution = explode("x", (string) $size["maxResolution"]); + $image->resize((int) $resolution[0], (int) $resolution[1], Image::SHRINK_ONLY | Image::FIT); + } else { + throw new \RuntimeException("Malformed size description: " . (string) $size["id"]); + } + + $res[1] = $image->getWidth(); + $res[2] = $image->getHeight(); + if($res[1] <= 300 || $res[2] <= 300) + $image->save("$outputDir/" . (string) $size["id"] . ".gif"); + else + $image->save("$outputDir/" . (string) $size["id"] . ".jpeg"); + + imagedestroy($image->getImageResource()); + unset($image); + + return $res; + } + + private function saveImageResizedCopies(string $filename, string $hash): void + { + $dir = dirname($this->pathFromHash($hash)); + $dir = "$dir/$hash" . "_cropped"; + if(!is_dir($dir)) + mkdir($dir); + + $sizes = simplexml_load_file(OPENVK_ROOT . "/data/photosizes.xml"); + if(!$sizes) + throw new \RuntimeException("Could not load photosizes.xml!"); + + $sizesMeta = []; + foreach($sizes->Size as $size) + $sizesMeta[(string) $size["id"]] = $this->resizeImage($filename, $dir, $size); + + $sizesMeta = MessagePack::pack($sizesMeta); + $this->stateChanges("sizes", $sizesMeta); + } + protected function saveFile(string $filename, string $hash): bool { $image = Image::fromFile($filename); if(($image->height >= ($image->width * Photo::ALLOWED_SIDE_MULTIPLIER)) || ($image->width >= ($image->height * Photo::ALLOWED_SIDE_MULTIPLIER))) throw new ISE("Invalid layout: image is too wide/short"); - + + $image->resize(8192, 4320, Image::SHRINK_ONLY | Image::FIT); $image->save($this->pathFromHash($hash), 92, Image::JPEG); + $this->saveImageResizedCopies($filename, $hash); + $this->getDimensions(); # propagate dimensions info in DB return true; } @@ -46,23 +107,98 @@ class Photo extends Media DB::i()->getContext()->table("album_relations")->where("media", $this->getRecord()->id)->delete(); } - function getDimensions(): array - { + function getSizes(): ?array + { + $sizes = $this->getRecord()->sizes; + if(!$sizes) { + if(OPENVK_ROOT_CONF["openvk"]["preferences"]["photos"]["upgradeStructure"]) { + $hash = $this->getRecord()->hash; + $this->saveImageResizedCopies($this->pathFromHash($hash), $hash); + $this->save(); + + return $this->getSizes(); + } + + return NULL; + } + + $res = []; + $sizes = MessagePack::unpack($sizes); + foreach($sizes as $id => $meta) { + $url = $this->getURL(); + $url = str_replace(".$this->fileExtension", "_cropped/$id.", $url); + $url .= ($meta[1] <= 300 || $meta[2] <= 300) ? "gif" : "jpeg"; + + $res[$id] = (object) [ + "url" => $url, + "width" => $meta[1], + "height" => $meta[2], + "crop" => $meta[0] + ]; + } + + [$x, $y] = $this->getDimensions(); + $res["UPLOADED_MAXRES"] = (object) [ + "url" => $this->getURL(), + "width" => $x, + "height" => $y, + "crop" => false + ]; + + return $res; + } + + function getVkApiSizes(): ?array + { + $res = []; + $sizes = $this->getSizes(); + if(!$sizes) + return NULL; + + $manifest = simplexml_load_file(OPENVK_ROOT . "/data/photosizes.xml"); + if(!$manifest) + return NULL; + + $mappings = []; + foreach($manifest->Size as $size) + $mappings[(string) $size["id"]] = (string) $size["vkId"]; + + foreach($sizes as $id => $meta) + $res[$mappings[$id] ?? $id] = $meta; + + return $res; + } + + function getURLBySizeId(string $size): string + { + $sizes = $this->getSizes(); + if(!$sizes) + return $this->getURL(); + + $size = $sizes[$size]; + if(!$size) + return $this->getURL(); + + return $size->url; + } + + function getDimensions(): array + { $x = $this->getRecord()->width; $y = $this->getRecord()->height; if(!$x) { # no sizes in database $hash = $this->getRecord()->hash; - $image = new \Imagick($this->pathFromHash($hash)); + $image = Image::fromFile($this->pathFromHash($hash)); - $x = $image->getImageWidth(); - $y = $image->getImageHeight(); + $x = $image->getWidth(); + $y = $image->getHeight(); $this->stateChanges("width", $x); $this->stateChanges("height", $y); $this->save(); } return [$x, $y]; - } + } function getAlbum(): ?Album { @@ -80,10 +216,14 @@ class Photo extends Media $res->height = $this->getDimensions()[1]; $res->date = $res->created = $this->getPublicationTime()->timestamp(); - $res->src = - $res->src_small = $res->src_big = $res->src_xbig = $res->src_xxbig = - $res->src_xxxbig = $res->photo_75 = $res->photo_130 = $res->photo_604 = - $res->photo_807 = $res->photo_1280 = $res->photo_2560 = $this->getURL(); + $res->sizes = $this->getVkApiSizes(); + $res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule"); + $res->src = $res->photo_130 = $this->getURLBySizeId("tiny"); + $res->src_big = $res->photo_604 = $this->getURLBySizeId("normal"); + $res->src_xbig = $res->photo_807 = $this->getURLBySizeId("large"); + $res->src_xxbig = $res->photo_1280 = $this->getURLBySizeId("larger"); + $res->src_xxxbig = $res->photo_2560 = $this->getURLBySizeId("original"); + $res->src_original = $res->url = $this->getURLBySizeId("UPLOADED_MAXRES"); return $res; } diff --git a/composer.json b/composer.json index 7cfa85e7..d6e2d5e5 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,8 @@ "chillerlan/php-qrcode": "dev-main", "vearutop/php-obscene-censor-rus": "dev-master", "erusev/parsedown": "dev-master", - "bhaktaraz/php-rss-generator": "dev-master" + "bhaktaraz/php-rss-generator": "dev-master", + "ext-simplexml": "*" }, "minimum-stability": "dev" } diff --git a/data/photosizes.xml b/data/photosizes.xml new file mode 100644 index 00000000..1a85a56c --- /dev/null +++ b/data/photosizes.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/install/sqls/00020-image-sizes.sql b/install/sqls/00020-image-sizes.sql index e2838f05..5dee8f18 100644 --- a/install/sqls/00020-image-sizes.sql +++ b/install/sqls/00020-image-sizes.sql @@ -1,3 +1,3 @@ ALTER TABLE `photos` ROW_FORMAT=COMPRESSED; -ALTER TABLE `photos` ADD COLUMN `sizes` VARBINARY(256) NULL DEFAULT NULL AFTER `hash`; +ALTER TABLE `photos` ADD COLUMN `sizes` VARBINARY(486) NULL DEFAULT NULL AFTER `hash`; ALTER TABLE `photos` ADD COLUMN `width` SMALLINT UNSIGNED NULL DEFAULT NULL AFTER `sizes`, ADD COLUMN `height` SMALLINT UNSIGNED NULL DEFAULT NULL AFTER `sizes`; diff --git a/openvk-example.yml b/openvk-example.yml index 0e8e8c46..b5ae676b 100644 --- a/openvk-example.yml +++ b/openvk-example.yml @@ -17,6 +17,8 @@ openvk: minLength: 3 # won't affect existing short urls or the ones set via admin panel forbiddenNames: - "index.php" + photos: + upgradeStructure: true security: requireEmail: false requirePhone: false