package ONO::Lib::Image::Magick;
################################################################################
# COPYRIGHT / LICENSE #
################################################################################
#
# This file is part of the ONO Software Project.
#
# Copyright (C) 2000-2025 Jos KIRPS [ www.kirps.com | jos_AT_kirps_DOT_com ]
# and The Joopita Project [ www.joopita.org | contact_AT_joopita_DOT_com ]
#
# This file, as well as other parts of the ONO Software Project or related
# elements, are FREE SOFTWARE available under the ARTISTIC LICENSE 2.0.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# For the full license, see /ono/osr/license/LICENSE.txt, or write to
# jos_AT_kirps_DOT_com or contact_AT_joopita_DOT_com.
#
################################################################################
# END OF COPYRIGHT / LICENSE, HERE COMES THE CODE ... #
################################################################################
use strict;
use ONO::IO;
use ONO::Core;
use ONO::Lib::Image::Size;
use ONO::Lib::Image::ToolBox;
use ONO::Lib::Video::ToolBox;
use ONO::ToolBox::Docs;
###############################################################################
# disable all...
###############################################################################
my $disable;
#$disable++;
###############################################################################
# image info
###############################################################################
sub getinfo {
my (
$self,
$file,
$switches,
) = @_;
#: Return image info, using the Image Magick 'identify' tool.
my $INFO;
my $TEST = $file;
$TEST =~ s~[^A-Za-z0-9\-\_\.\/]~~g;
if (!$disable && ONO::IO->image($file) && $file eq $TEST) {
my $vpath = ONO::IO->path();
my $COM = "identify '$vpath/$file'";
$INFO = ONO::IO->exec2($COM);
}
return $INFO;
}
###############################################################################
# convert an image
###############################################################################
sub convert {
my (
$self,
$file,
$target,
$switches,
$option,
) = @_;
#: Convert an image, by generating a JPG image in most cases.
#:
#: Available switches:
#:
#: -F only convert the First page of a PDF file
#: -d debug on devstation
#: -D delete source file after conversion (only if successful)
#: -h half quality (PDF only, can be combined with -H)
#: -H quarter quality (PDF only, can be combined with -h)
#: -Q QR code mode - important, as images need to be converted to RGB
#: -R force RGB conversion (also see -V)
#: -S Size force to 920 for HEIC images
#: -V use the Video library (ffmpeg) instead of Image::Magick for RGB conversion
#:
#: Important note regarding the handling of PDF documents:
#: In order to read/write PDF files, the IM's policy file may have to bechanged:
#: /etc/ImageMagick-VER/policy.xml
my $DEBUG = "ImageMagick,";
if (ONO::IO->exists("etc/imagemagick_disable.conf")) {
$disable++;
$DEBUG .= "disabled,";
}
my $return;
if (!$disable) {
$DEBUG .= "running,";
# use image magick for the most common stuff
if ($file =~ /\.(gif|png|jpg|pdf|webp)$/i && $target =~ /\.(gif|png|jpg|webp)$/i) {
my $vpath = ONO::IO->path;
$DEBUG .= "file=,$vpath$file,";
eval "use Image::Magick";
if (!$@ && -e "$vpath$file") {
$DEBUG .= "installed,";
if ($file =~ /\.pdf$/i) {
# 118x118 is about 300dpi
my $PAGE;
if ($switches =~ /F/) {
$PAGE = "[0]";
}
my $DENSITY = "236x236";
if ($switches =~ /h/) {
$DENSITY = "118x118";
}
if ($switches =~ /H/) {
$DENSITY = "59x59";
if ($switches =~ /H/) {
$DENSITY = "32x32";
}
}
my $COM = "convert -colorspace sRGB -density $DENSITY -units pixelspercentimeter -background white -alpha remove '$vpath$file$PAGE' '$vpath$target'";
ONO::IO->exec2($COM);
$DEBUG .= "pdf,$COM";
} else {
$DEBUG .= "img,";
if ($switches =~ /Q/) {
my $magick = Image::Magick->new;
$magick -> Read("$vpath$file");
$magick -> Write(filename => "$vpath$target", colorspace => 'sRGB', quality => 100);
my $COM = "convert -colorspace sRGB -type truecolor -scale 512 -background white -alpha remove '$vpath$target' '$vpath$target'";
ONO::IO->exec2($COM);
$DEBUG .= "qr,$COM";
} else {
if ($switches =~ /R/) {
if ($switches =~ /V/) {
my ($vpath,$ffmpeg) = (ONO::IO->path,ONO::Lib::Video::ToolBox->detect_ffmpeg);
my $COM = "$ffmpeg -i '$vpath$file' '$vpath$target'";
# $DEBUG .= $COM;
ONO::IO->exec2($COM);
$DEBUG .= "rgb/ffmpeg, $vpath$file, $vpath$target";
} else {
# this does NOT work properly yet, don't know why ???
my $COM = "convert -colorspace sRGB -type truecolor -profile sRGB.icc '$vpath$file' '$vpath$target'";
# $DEBUG .= $COM;
$DEBUG .= "rgb, $vpath$file, $vpath$target";
ONO::IO->exec2($COM);
}
} else {
my $magick = Image::Magick->new;
$magick -> Read("$vpath$file");
$magick -> Write(filename => "$vpath$target", quality => 90);
# , colorspace => 'sRGB'
$DEBUG .= "std, $vpath$file, $vpath$target";
}
}
}
$return = $target;
}
}
# use heif-convert for heic to jpeg images (requires sudo apt-get install libheif-examples)
if ($file =~ /\.(heic)$/i && $target =~ /\.(jpg)$/i) {
my ($vpath,$heif) = (ONO::IO->path,ONO::Lib::Image::ToolBox->detect_heif_convert);
$DEBUG .= "heic mode for $file, vpath = $vpath, heif_convert = $heif, ";
if ($vpath && $heif) {
# quality is automatically set to 50%, in order to reduce file size
system("$heif -q 50 '$vpath$file' '$vpath$target'");
$DEBUG .= "executing: $heif '$vpath$file' '$vpath$target'";
if ($switches =~ /S/) {
&resize("",$target,$target,"max:920","max:920");
}
}
}
# use wkhtmltoimage to turn URLs (web pages) into images
if ($file =~ m~^(http|https)://~ && $target =~ /\.(jpg)$/i) {
my ($vpath,$wkhtmltoimage) = (ONO::IO->path,ONO::Lib::Image::ToolBox->detect_wkhtmltoimage);
$DEBUG .= "wkhtmltoimage mode for $file, vpath = $vpath, wkhtmltoimage = $wkhtmltoimage, ";
if ($vpath && $wkhtmltoimage) {
my $HEIGHT;
if ($option =~ /[0-9]/) {
$option =~ s~[^0-9]~~g;
$HEIGHT = "--height $option";
}
system("$wkhtmltoimage -q $HEIGHT '$file' '$vpath$target'");
$DEBUG .= "executing: $wkhtmltoimage -q $HEIGHT '$file' '$vpath$target'";
}
}
if ($switches =~ /D/ && ONO::IO->exists($target)) {
ONO::IO->rm($file);
}
}
if (ONO::IO->devstation && $switches =~ /d/) {
return $DEBUG;
} else {
return $return;
}
}
###############################################################################
# resize an image
###############################################################################
sub resize {
my (
$self,
$file,
$target,
$width,
$height,
$switches,
) = @_;
#: Resize an image.
if (ONO::IO->exists("etc/imagemagick_disable.conf")) {
$disable++;
}
if (!$disable) {
my $vpath = ONO::IO->path;
if (!$target) {
$target = $file;
}
my $in_width = $width;
my $in_height = $height;
my ($status,$dim_x,$dim_y);
my $debugger = "ONO_Lib_Image_Magick_resize ! called for '$file'<br>";
if ((($width eq "#" || $height eq "#") && ($width > 1 || $height > 1)) || $width =~ /^max\:/ || $height =~ /^max\:/) {
if ($file =~ /\.pdf$/i) {
$dim_x = 210;
$dim_y = 297;
} else {
(
$status,
$dim_x,
$dim_y,
) = ONO::Lib::Image::Size->size($file);
}
$debugger .= "ONO_Lib_Image_Magick_resize ! ($dim_x*$dim_y -> $width*$height)<br>";
$width = $dim_x;
$height = $dim_y;
if ($dim_x && $dim_y) {
if ($in_width eq "#") {
$width = int($dim_x/$dim_y*$height);
}
if ($in_height eq "#") {
$height = int($dim_y/$dim_x*$width);
}
if ($in_width =~ /^max:(.*?)$/) {
if ($width > $1) {
$width = $1;
$height = int($dim_y/$dim_x*$1);
}
}
if ($in_height =~ /^max:(.*?)$/) {
if ($height > $1) {
$height = $1;
$width = int($dim_x/$dim_y*$1);
}
}
}
$debugger .= "ONO_Lib_Image_Magick_resize ! ($dim_x*$dim_y -> $width*$height)<br>";
} else {
$debugger .= "ONO_Lib_Image_Magick_resize ! NO ANALYSIS<br>";
}
# only process if size changes, or if target is not source
my $process = 0;
if ($width < $dim_x || $height < $dim_y || $file ne $target || $width =~ /^square\:/) {
$process++;
}
if ($width =~ /^square\:(.*?)$/) {
$width = $1;
$height = $1;
}
$debugger .= "ONO_Lib_Image_Magick_resize ! process status: $process, width = $width, height = $height<br>";
# processor
eval "use Image::Magick";
if (!$@ && $process && ONO::IO->exists($file) && $width > 1 && $height > 1 && $file =~ /\.(jpg|png|gif|webp|pdf|tiff)$/ && ONO::IO->image($target)) {
if ($file =~ /\.pdf/) {
my $convert = ONO::IO->which('convert');
my $COM = "$convert -colorspace sRGB -resize ${width}x${height} -background white -alpha remove '$vpath$file\[0]' '$vpath$target'";
ONO::IO->exec2($COM);
$debugger .= "ONO_Lib_Image_Magick_resize ! shell command: $COM<br>";
} else {
my $magick = Image::Magick->new;
$magick -> Read("$vpath$file");
$magick -> Resize(width => $width, height => $height);
$magick -> Write(filename => "$vpath$target", quality => 90);
$debugger .= "ONO_Lib_Image_Magick_resize ! using Image::Magick (non-PDF)<br>";
}
$debugger .= "ONO_Lib_Image_Magick_resize ! input: $vpath$file<br>";
$debugger .= "ONO_Lib_Image_Magick_resize ! output: $vpath$target<br>";
$debugger .= "ONO_Lib_Image_Magick_resize ! running Image::Magick<br>";
if (ONO::IO->exists($target)) {
$debugger .= "ONO_Lib_Image_Magick_resize ! SUCCESS<br>";
} else {
$debugger .= "ONO_Lib_Image_Magick_resize ! ERROR: NOTHING SAVED<br>";
}
} else {
$debugger .= "ONO_Lib_Image_Magick_resize ! ABORT<br>";
}
$debugger .= "ONO_Lib_Image_Magick_resize ! DONE<br>";
return ($process,$width,$height,$debugger);
}
}
###############################################################################
# action
###############################################################################
sub action {
my (
$self,
$file,
$target,
$option,
$switches,
) = @_;
#: Image actions: rotate, or flip.
#:
#: Available switches:
#:
#: -t re-generate thumbs
if (ONO::IO->exists("etc/imagemagick_disable.conf")) {
$disable++;
}
my $DEBUG;
if (!$disable && $option =~ /^(rotate_left|rotate_right|rotate_180|flip_hori|flip_vert)$/) {
my $vpath = ONO::IO->path;
if (!$target) {
$target = $file;
}
$DEBUG .= qq~ImageMagickAction -> $vpath/$file -> $target, ~;
# processor
eval "use Image::Magick";
if (!$@ && -e "$vpath/$file" && ONO::IO->image($file) && ONO::IO->image($target)) {
$DEBUG .= qq~file exists, processing option = $option, ~;
my $magick = Image::Magick->new;
$magick -> Read("$vpath$file");
if ($option eq "rotate_left") {
$magick -> Rotate(degrees => -90);
}
if ($option eq "rotate_right") {
$magick -> Rotate(degrees => 90);
}
if ($option eq "rotate_180") {
$magick -> Rotate(degrees => 180);
}
if ($option eq "flip_hori") {
$magick -> Flop;
}
if ($option eq "flip_vert") {
$magick -> Flip;
}
$magick -> Write(filename => "$vpath$target", quality => 90);
if ($switches =~ /t/) {
my ($pr,$tn) = ONO::IO->getthumbs($target);
if ($pr || $tn) {
ONO::ToolBox::Docs->mkthumb($target);
}
}
$DEBUG .= qq~DONE!~;
ONO::IO->store("var/log/ono_lib_image_magick_action.txt","EXEC: $file -> $target, option = $option");
} else {
ONO::IO->store("var/log/ono_lib_image_magick_action.txt","ABORT(2): $file -> $target, option = $option");
}
} else {
ONO::IO->store("var/log/ono_lib_image_magick_action.txt","ABORT(1): $file -> $target, option = $option");
}
if (ONO::IO->devstation) {
ONO::IO->store("image_magick_action.txt",$DEBUG);
}
return 1;
}
sub rotate {
my (
$self,
$file,
$target,
$rotate,
$switches,
) = @_;
#: Rotate image, using the ImageMagick convert/magick command directly.
#: This can be used as an alternative to the action tool.
$file =~ s~[^A-Za-z0-9\-\_\.\/]~~g;
my ($vpath,$convert) = (ONO::IO->path,&detect_convert);
my $COM = qq~image_magick_rotate: ABORT: $vpath, $convert, $file, $target, $rotate~;
if ($vpath && $convert && ONO::IO->image($file) && ONO::IO->image($target) && ONO::IO->exists($file) && $rotate) {
$COM = qq~$convert '$vpath/$file' -rotate $rotate -auto-orient '$vpath/$target'~;
`$convert '$vpath/$file' -rotate $rotate -auto-orient '$vpath/$target'`;
}
if (ONO::IO->devstation) {
ONO::IO->store("_image_magick_rotate.txt",$COM);
}
}
sub detect_convert {
#: Detect if the convert or magick command is installed.
#: This will prefer the magick command, if available.
my ($convert,$magick);
foreach my $path (ONO::IO->binary_dirs()) {
if (-e "$path/convert") {
$convert = "$path/convert";
}
}
foreach my $path (ONO::IO->binary_dirs()) {
if (-e "$path/magick") {
$magick = "$path/magick";
}
}
if ($magick) {
$convert = $magick;
}
return $convert;
}
###############################################################################
# end of script
###############################################################################
1;
__END__