From 8f156026a834d0494ff9ba2ee2686f163af62702 Mon Sep 17 00:00:00 2001 From: Nik Gupta Date: Mon, 22 Apr 2024 16:56:19 +0100 Subject: [PATCH] [Waste] Limit image size on Echo backed Bulky Collection reports. https://github.com/mysociety/societyworks/issues/4243 --- perllib/FixMyStreet/App/Model/PhotoSet.pm | 27 +++++++++ perllib/FixMyStreet/ImageMagick.pm | 13 ++++ perllib/FixMyStreet/Roles/CobrandEcho.pm | 23 ++++++++ .../Roles/EnforcePhotoSizeOpen311PreSend.pm | 59 +++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 perllib/FixMyStreet/Roles/EnforcePhotoSizeOpen311PreSend.pm diff --git a/perllib/FixMyStreet/App/Model/PhotoSet.pm b/perllib/FixMyStreet/App/Model/PhotoSet.pm index bbe60ca7795..1c4bc25607a 100644 --- a/perllib/FixMyStreet/App/Model/PhotoSet.pm +++ b/perllib/FixMyStreet/App/Model/PhotoSet.pm @@ -416,4 +416,31 @@ sub redact_image { return $new_set; } +# Repeatedly shrink any images over the given size by the given percentage +# until they are small enough. +# Returns the new photoset and a bool indiciating if any images were shrunk. +sub shrink_all_to_size { + my ($self, $size_bytes, $resize_percent) = @_; + + my $shrunk = 0; + my @images = $self->all_ids; + foreach my $i (0.. $#images) { + my $blob = $self->get_raw_image($i)->{data}; + while (length $blob > $size_bytes) { + $blob = FixMyStreet::ImageMagick->new(blob => $blob) + ->shrink_to_percentage($resize_percent) + ->as_blob; + $images[$i] = $blob; + $shrunk = 1; + } + } + + my $new_set = (ref $self)->new({ + data_items => \@images, + object => $self->object, + }); + $self->delete_cached(); + return ($new_set, $shrunk); +} + 1; diff --git a/perllib/FixMyStreet/ImageMagick.pm b/perllib/FixMyStreet/ImageMagick.pm index b6e7c87f252..503ef0a9f92 100644 --- a/perllib/FixMyStreet/ImageMagick.pm +++ b/perllib/FixMyStreet/ImageMagick.pm @@ -63,6 +63,19 @@ sub shrink { return $self->strip; } +# Shrinks a picture to the specified percentage of the original, but keeping in proportion. +sub shrink_to_percentage { + my ($self, $percentage) = @_; + return $self unless $self->image; + + my ($width, $height) = $self->image->Get('width', 'height'); + my $new_width = int($width * $percentage / 100); + my $new_height = int($height * $percentage / 100); + + my $err = $self->image->Scale(width => $new_width, height => $new_height); + return $self->strip; +} + # Shrinks a picture to a given dimension (defaults to 90x60(, cropping so that # it is exactly that. sub crop { diff --git a/perllib/FixMyStreet/Roles/CobrandEcho.pm b/perllib/FixMyStreet/Roles/CobrandEcho.pm index 5ebfd31a834..970c3a2065d 100644 --- a/perllib/FixMyStreet/Roles/CobrandEcho.pm +++ b/perllib/FixMyStreet/Roles/CobrandEcho.pm @@ -4,13 +4,17 @@ use v5.14; use warnings; use DateTime; use DateTime::Format::Strptime; +use List::Util qw(min); use Moo::Role; +use POSIX qw(floor); use Sort::Key::Natural qw(natkeysort_inplace); use FixMyStreet::DateRange; use FixMyStreet::DB; use FixMyStreet::WorkingDays; use Open311::GetServiceRequestUpdates; +with 'FixMyStreet::Roles::EnforcePhotoSizeOpen311PreSend'; + requires 'waste_containers'; requires 'waste_service_to_containers'; requires 'waste_quantity_max'; @@ -1212,4 +1216,23 @@ sub send_bulky_payment_echo_update_failed { } } +around per_photo_size_limit_for_report_in_bytes => sub { + my ($orig, $self, $report, $image_count) = @_; + + # We only need to check bulky collections at present. + return $self->$orig($report, $image_count) unless $report->cobrand_data eq 'waste' && $report->contact->category eq 'Bulky collection'; + + my $cfg = FixMyStreet->config('COBRAND_FEATURES'); + return 0 unless $cfg; + + my $echo_cfg = $cfg->{'echo'}; + return 0 unless $echo_cfg; + + my $max_size_per_image = $echo_cfg->{'max_size_per_image_bytes'}; + my $max_size_images_total = $echo_cfg->{'max_size_image_total_bytes'}; + return 0 unless $max_size_per_image && $max_size_images_total; + + return ($max_size_per_image, floor($max_size_images_total / $image_count)); +}; + 1; diff --git a/perllib/FixMyStreet/Roles/EnforcePhotoSizeOpen311PreSend.pm b/perllib/FixMyStreet/Roles/EnforcePhotoSizeOpen311PreSend.pm new file mode 100644 index 00000000000..5f43ec64e1f --- /dev/null +++ b/perllib/FixMyStreet/Roles/EnforcePhotoSizeOpen311PreSend.pm @@ -0,0 +1,59 @@ +package FixMyStreet::Roles::EnforcePhotoSizeOpen311PreSend; +use Moo::Role; + +=head1 NAME + +FixMyStreet::Roles::EnforcePhotoSizeOpen311PreSend - limit report photo sizes on open311 pre-send + +=head1 SYNOPSIS + +Applied to a cobrand class to shrink any images larger than a given size as an open311 pre-send action. + +Oversized images are repeatedly shrunk until they conform. + +A 'photo_size_limit_applied_' metadata flag is set on the report to indicate it has been processed +and prevent reprocessing. + +=cut + +=head1 REQUIRED METHODS + +=cut + +=head2 per_photo_size_limit_for_report_in_bytes + +Takes the report and the number of images. +Returns the max number of bytes for each photo on the report. +0 indicates no max to apply. + +=cut + +sub per_photo_size_limit_for_report_in_bytes { 0 } + +sub open311_pre_send { } + +after open311_pre_send => sub { + my ($self, $report, $open311) = @_; + my $photoset = $report->get_photoset; + return unless $photoset->num_images > 0; + + my $limit = $self->per_photo_size_limit_for_report_in_bytes($report, $photoset->num_images); + return unless $limit > 0; + + my $limit_applied_flag = "photo_size_limit_applied_" . $limit; + return if $report->get_extra_metadata($limit_applied_flag); + + # Keep shrinking oversized images to 90% of their original size until they conform. + my ($new, $shrunk) = $photoset->shrink_all_to_size($limit, 90); + + if ($shrunk) { + $report->update({ photo => $new }); + } + + $report->set_extra_metadata( $limit_applied_flag => 1 ); + $report->update; +}; + + + +1;