From 801745a1985deb8eb7a3a3112fce11bff7225888 Mon Sep 17 00:00:00 2001 From: Emi Simpson Date: Sun, 14 Aug 2022 17:05:12 -0400 Subject: [PATCH] Include image format information --- protobuf/image.proto | 9 +++ src/main.rs | 20 +++--- src/protobuf/image.rs | 140 ++++++++++++++++++++++++++++++++++++------ src/thumbnailing.rs | 39 +++++++++--- 4 files changed, 173 insertions(+), 35 deletions(-) diff --git a/protobuf/image.proto b/protobuf/image.proto index 26e1c83..8ae93c2 100644 --- a/protobuf/image.proto +++ b/protobuf/image.proto @@ -1,8 +1,17 @@ syntax = "proto3"; +enum Format { + WEBP = 0; + AVIF = 1; + JPG = 2; + PNG = 3; + GIF = 4; +} + message Image { bytes key = 1; string full_url = 2; string thumb_url = 3; string blurhash = 4; + Format format = 5; } diff --git a/src/main.rs b/src/main.rs index 0120aa7..2001739 100644 --- a/src/main.rs +++ b/src/main.rs @@ -89,42 +89,44 @@ fn create(server: &str, args: CreateArgs) { .inspect(|r| if r.is_ok() { print!("\x1b[32mDone!\n\x1b[37m├─\x1b[0m Thumbnailing... ") }) .inspect(|_| drop(stdout().flush())) .map(|r| r.and_then(|(path, raw_dat)| { - let (thumbnail, blurhash) = thumbnailing::thumbnail(&raw_dat) + let (full, thumbnail, blurhash, format) = thumbnailing::thumbnail(&raw_dat) .map_err(|_| AviaryError::ImageFormatError(path.to_owned()))?; - Ok((raw_dat, thumbnail, blurhash)) + Ok((full.map(Either::Right).unwrap_or(Either::Left(raw_dat)), thumbnail, blurhash, format)) })) .inspect(|r| if r.is_ok() { print!("\x1b[32mDone!\n\x1b[37m├─\x1b[0m Encrypting... ")}) .inspect(|_| drop(stdout().flush())) - .map_ok(|(raw_dat, thumbnail, blurhash)| { + .map_ok(|(raw_dat, thumbnail, blurhash, format)| { let key = crypto::make_key(); ( key, crypto::encrypt(&key, &raw_dat), crypto::encrypt(&key, &thumbnail), - blurhash + blurhash, + format ) }) .inspect(|r| if r.is_ok() { print!("\x1b[32mDone!\n\x1b[37m└─\x1b[0m Uploading... ")}) .inspect(|_| drop(stdout().flush())) - .map(|r| r.and_then(|(key, full_img, thumb, blurhash)| + .map(|r| r.and_then(|(key, full_img, thumb, blurhash, format)| upload::put_data(&agent, server, &thumb) .and_then(|thumb_url| upload::put_data(&agent, server, &full_img) - .map(|full_url| (key, full_url, thumb_url, blurhash))) + .map(|full_url| (key, full_url, thumb_url, blurhash, format))) .map_err(AviaryError::from_upload_error) )) - .map(|r| r.and_then(|(key, full_url, thumb_url, blurhash)| { + .map(|r| r.and_then(|(key, full_url, thumb_url, blurhash, format)| { let full_trimmed = trim_url(server, &full_url); let thmb_trimmed = trim_url(server, &thumb_url); if let (Some(full_url), Some(thmb_url)) = (full_trimmed, thmb_trimmed) { - Ok((key, full_url.to_owned(), thmb_url.to_owned(), blurhash)) + Ok((key, full_url.to_owned(), thmb_url.to_owned(), blurhash, format)) } else { Err(AviaryError::ServerError(format!("Received bad response from server: {}", full_url))) } })) .inspect(|r| if r.is_ok() { println!("\x1b[32mDone!\n")}) - .map_ok(|(key, full_url, thumb_url, blurhash)| protobuf::image::Image { + .map_ok(|(key, full_url, thumb_url, blurhash, format)| protobuf::image::Image { key: key.into(), + format: format.into(), full_url, thumb_url, blurhash, special_fields: Default::default() }) diff --git a/src/protobuf/image.rs b/src/protobuf/image.rs index 426c27d..295202c 100644 --- a/src/protobuf/image.rs +++ b/src/protobuf/image.rs @@ -37,6 +37,8 @@ pub struct Image { pub thumb_url: ::std::string::String, // @@protoc_insertion_point(field:Image.blurhash) pub blurhash: ::std::string::String, + // @@protoc_insertion_point(field:Image.format) + pub format: ::protobuf::EnumOrUnknown, // special fields // @@protoc_insertion_point(special_field:Image.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -54,7 +56,7 @@ impl Image { } fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(4); + let mut fields = ::std::vec::Vec::with_capacity(5); let mut oneofs = ::std::vec::Vec::with_capacity(0); fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( "key", @@ -76,6 +78,11 @@ impl Image { |m: &Image| { &m.blurhash }, |m: &mut Image| { &mut m.blurhash }, )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "format", + |m: &Image| { &m.format }, + |m: &mut Image| { &mut m.format }, + )); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "Image", fields, @@ -106,6 +113,9 @@ impl ::protobuf::Message for Image { 34 => { self.blurhash = is.read_string()?; }, + 40 => { + self.format = is.read_enum_or_unknown()?; + }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; }, @@ -130,6 +140,9 @@ impl ::protobuf::Message for Image { if !self.blurhash.is_empty() { my_size += ::protobuf::rt::string_size(4, &self.blurhash); } + if self.format != ::protobuf::EnumOrUnknown::new(Format::WEBP) { + my_size += ::protobuf::rt::int32_size(5, self.format.value()); + } my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); self.special_fields.cached_size().set(my_size as u32); my_size @@ -148,6 +161,9 @@ impl ::protobuf::Message for Image { if !self.blurhash.is_empty() { os.write_string(4, &self.blurhash)?; } + if self.format != ::protobuf::EnumOrUnknown::new(Format::WEBP) { + os.write_enum(5, ::protobuf::EnumOrUnknown::value(&self.format))?; + } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) } @@ -169,6 +185,7 @@ impl ::protobuf::Message for Image { self.full_url.clear(); self.thumb_url.clear(); self.blurhash.clear(); + self.format = ::protobuf::EnumOrUnknown::new(Format::WEBP); self.special_fields.clear(); } @@ -178,6 +195,7 @@ impl ::protobuf::Message for Image { full_url: ::std::string::String::new(), thumb_url: ::std::string::String::new(), blurhash: ::std::string::String::new(), + format: ::protobuf::EnumOrUnknown::from_i32(0), special_fields: ::protobuf::SpecialFields::new(), }; &instance @@ -201,24 +219,107 @@ impl ::protobuf::reflect::ProtobufValue for Image { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } +#[derive(Clone,Copy,PartialEq,Eq,Debug,Hash)] +// @@protoc_insertion_point(enum:Format) +pub enum Format { + // @@protoc_insertion_point(enum_value:Format.WEBP) + WEBP = 0, + // @@protoc_insertion_point(enum_value:Format.AVIF) + AVIF = 1, + // @@protoc_insertion_point(enum_value:Format.JPG) + JPG = 2, + // @@protoc_insertion_point(enum_value:Format.PNG) + PNG = 3, + // @@protoc_insertion_point(enum_value:Format.GIF) + GIF = 4, +} + +impl ::protobuf::Enum for Format { + const NAME: &'static str = "Format"; + + fn value(&self) -> i32 { + *self as i32 + } + + fn from_i32(value: i32) -> ::std::option::Option { + match value { + 0 => ::std::option::Option::Some(Format::WEBP), + 1 => ::std::option::Option::Some(Format::AVIF), + 2 => ::std::option::Option::Some(Format::JPG), + 3 => ::std::option::Option::Some(Format::PNG), + 4 => ::std::option::Option::Some(Format::GIF), + _ => ::std::option::Option::None + } + } + + const VALUES: &'static [Format] = &[ + Format::WEBP, + Format::AVIF, + Format::JPG, + Format::PNG, + Format::GIF, + ]; +} + +impl ::protobuf::EnumFull for Format { + fn enum_descriptor() -> ::protobuf::reflect::EnumDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().enum_by_package_relative_name("Format").unwrap()).clone() + } + + fn descriptor(&self) -> ::protobuf::reflect::EnumValueDescriptor { + let index = *self as usize; + Self::enum_descriptor().value_by_index(index) + } +} + +impl ::std::default::Default for Format { + fn default() -> Self { + Format::WEBP + } +} + +impl Format { + fn generated_enum_descriptor_data() -> ::protobuf::reflect::GeneratedEnumDescriptorData { + ::protobuf::reflect::GeneratedEnumDescriptorData::new::("Format") + } +} + static file_descriptor_proto_data: &'static [u8] = b"\ - \n\x0bimage.proto\"m\n\x05Image\x12\x10\n\x03key\x18\x01\x20\x01(\x0cR\ - \x03key\x12\x19\n\x08full_url\x18\x02\x20\x01(\tR\x07fullUrl\x12\x1b\n\t\ - thumb_url\x18\x03\x20\x01(\tR\x08thumbUrl\x12\x1a\n\x08blurhash\x18\x04\ - \x20\x01(\tR\x08blurhashJ\x86\x02\n\x06\x12\x04\0\0\x07\x01\n\x08\n\x01\ - \x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x07\x01\n\n\n\x03\x04\ - \0\x01\x12\x03\x02\x08\r\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x08\x16\n\ - \x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x08\r\n\x0c\n\x05\x04\0\x02\0\x01\ - \x12\x03\x03\x0e\x11\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x14\x15\n\ - \x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x08\x1c\n\x0c\n\x05\x04\0\x02\x01\ - \x05\x12\x03\x04\x08\x0e\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0f\ - \x17\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x1a\x1b\n\x0b\n\x04\x04\0\ - \x02\x02\x12\x03\x05\x08\x1d\n\x0c\n\x05\x04\0\x02\x02\x05\x12\x03\x05\ - \x08\x0e\n\x0c\n\x05\x04\0\x02\x02\x01\x12\x03\x05\x0f\x18\n\x0c\n\x05\ - \x04\0\x02\x02\x03\x12\x03\x05\x1b\x1c\n\x0b\n\x04\x04\0\x02\x03\x12\x03\ - \x06\x08\x1c\n\x0c\n\x05\x04\0\x02\x03\x05\x12\x03\x06\x08\x0e\n\x0c\n\ - \x05\x04\0\x02\x03\x01\x12\x03\x06\x0f\x17\n\x0c\n\x05\x04\0\x02\x03\x03\ - \x12\x03\x06\x1a\x1bb\x06proto3\ + \n\x0bimage.proto\"\x8e\x01\n\x05Image\x12\x10\n\x03key\x18\x01\x20\x01(\ + \x0cR\x03key\x12\x19\n\x08full_url\x18\x02\x20\x01(\tR\x07fullUrl\x12\ + \x1b\n\tthumb_url\x18\x03\x20\x01(\tR\x08thumbUrl\x12\x1a\n\x08blurhash\ + \x18\x04\x20\x01(\tR\x08blurhash\x12\x1f\n\x06format\x18\x05\x20\x01(\ + \x0e2\x07.FormatR\x06format*7\n\x06Format\x12\x08\n\x04WEBP\x10\0\x12\ + \x08\n\x04AVIF\x10\x01\x12\x07\n\x03JPG\x10\x02\x12\x07\n\x03PNG\x10\x03\ + \x12\x07\n\x03GIF\x10\x04J\xa2\x04\n\x06\x12\x04\0\0\x10\x01\n\x08\n\x01\ + \x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x08\x01\n\n\n\x03\x05\ + \0\x01\x12\x03\x02\x05\x0b\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x08\x11\n\ + \x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x08\x0c\n\x0c\n\x05\x05\0\x02\0\ + \x02\x12\x03\x03\x0f\x10\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x08\x11\n\ + \x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x08\x0c\n\x0c\n\x05\x05\0\x02\ + \x01\x02\x12\x03\x04\x0f\x10\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x05\x08\ + \x10\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x08\x0b\n\x0c\n\x05\x05\0\ + \x02\x02\x02\x12\x03\x05\x0e\x0f\n\x0b\n\x04\x05\0\x02\x03\x12\x03\x06\ + \x08\x10\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\x06\x08\x0b\n\x0c\n\x05\ + \x05\0\x02\x03\x02\x12\x03\x06\x0e\x0f\n\x0b\n\x04\x05\0\x02\x04\x12\x03\ + \x07\x08\x10\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x07\x08\x0b\n\x0c\n\ + \x05\x05\0\x02\x04\x02\x12\x03\x07\x0e\x0f\n\n\n\x02\x04\0\x12\x04\n\0\ + \x10\x01\n\n\n\x03\x04\0\x01\x12\x03\n\x08\r\n\x0b\n\x04\x04\0\x02\0\x12\ + \x03\x0b\x08\x16\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x0b\x08\r\n\x0c\n\ + \x05\x04\0\x02\0\x01\x12\x03\x0b\x0e\x11\n\x0c\n\x05\x04\0\x02\0\x03\x12\ + \x03\x0b\x14\x15\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x0c\x08\x1c\n\x0c\n\ + \x05\x04\0\x02\x01\x05\x12\x03\x0c\x08\x0e\n\x0c\n\x05\x04\0\x02\x01\x01\ + \x12\x03\x0c\x0f\x17\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x0c\x1a\x1b\n\ + \x0b\n\x04\x04\0\x02\x02\x12\x03\r\x08\x1d\n\x0c\n\x05\x04\0\x02\x02\x05\ + \x12\x03\r\x08\x0e\n\x0c\n\x05\x04\0\x02\x02\x01\x12\x03\r\x0f\x18\n\x0c\ + \n\x05\x04\0\x02\x02\x03\x12\x03\r\x1b\x1c\n\x0b\n\x04\x04\0\x02\x03\x12\ + \x03\x0e\x08\x1c\n\x0c\n\x05\x04\0\x02\x03\x05\x12\x03\x0e\x08\x0e\n\x0c\ + \n\x05\x04\0\x02\x03\x01\x12\x03\x0e\x0f\x17\n\x0c\n\x05\x04\0\x02\x03\ + \x03\x12\x03\x0e\x1a\x1b\n\x0b\n\x04\x04\0\x02\x04\x12\x03\x0f\x08\x1a\n\ + \x0c\n\x05\x04\0\x02\x04\x06\x12\x03\x0f\x08\x0e\n\x0c\n\x05\x04\0\x02\ + \x04\x01\x12\x03\x0f\x0f\x15\n\x0c\n\x05\x04\0\x02\x04\x03\x12\x03\x0f\ + \x18\x19b\x06proto3\ "; /// `FileDescriptorProto` object which was a source for this generated file @@ -238,7 +339,8 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { let mut deps = ::std::vec::Vec::with_capacity(0); let mut messages = ::std::vec::Vec::with_capacity(1); messages.push(Image::generated_message_descriptor_data()); - let mut enums = ::std::vec::Vec::with_capacity(0); + let mut enums = ::std::vec::Vec::with_capacity(1); + enums.push(Format::generated_enum_descriptor_data()); ::protobuf::reflect::GeneratedFileDescriptor::new_generated( file_descriptor_proto(), deps, diff --git a/src/thumbnailing.rs b/src/thumbnailing.rs index 558dda3..95be192 100644 --- a/src/thumbnailing.rs +++ b/src/thumbnailing.rs @@ -1,11 +1,15 @@ use std::io::Cursor; -use image::{io::Reader, DynamicImage, ImageResult}; +use image::{io::Reader, DynamicImage, ImageResult, ImageFormat}; use webp::{Encoder, WebPMemory}; -pub fn thumbnail(bytes: &[u8]) -> ImageResult<(WebPMemory, String)> { - let original_image = Reader::new(Cursor::new(bytes)) - .with_guessed_format().expect("IO errors impossible with Cursor") - .decode()?; +use crate::protobuf::image::Format; + +pub fn thumbnail(bytes: &[u8]) -> ImageResult<(Option, WebPMemory, String, Format)> { + let original_image_encoded = Reader::new(Cursor::new(bytes)) + .with_guessed_format().expect("IO errors impossible with Cursor"); + let original_format = original_image_encoded.format(); + let original_image = original_image_encoded.decode()?; + let new_dimm = u32::min(original_image.height(), original_image.width()); let crop_x = (original_image.width() - new_dimm) / 2; let crop_y = (original_image.height() - new_dimm) / 2; @@ -15,11 +19,32 @@ pub fn thumbnail(bytes: &[u8]) -> ImageResult<(WebPMemory, String)> { } else { original_image.thumbnail_exact(400, 400) }.into_rgba8(); - let blurhash = blurhash::encode(4, 4, 400, 400, scaled.as_raw()); + let scale = scaled.width(); + + let blurhash = blurhash::encode(4, 4, scale, scale, scaled.as_raw()); + + let (recoded_original, format) = match original_format { + Some(ImageFormat::WebP) => (None, Format::WEBP), + Some(ImageFormat::Avif) => (None, Format::AVIF), + Some(ImageFormat::Jpeg) => (None, Format::JPG), + Some(ImageFormat::Png) => (None, Format::PNG), + Some(ImageFormat::Gif) => (None, Format::GIF), + _ => ( // Unrecognized or format which isn't spec-supported + Some( + Encoder::from_image(&original_image) + .expect("Error transcoding image as webp") + .encode_lossless(), + ), + Format::WEBP + ) + }; + Ok(( + recoded_original, Encoder::from_image(&DynamicImage::ImageRgba8(scaled)) .expect("Unexpected difficulty interpretting thumbnail") .encode(50.0), - blurhash + blurhash, + format, )) }