imge: more updates

Signed-off-by: Varun Patil <varunpatil@ucla.edu>
pull/579/head
Varun Patil 2023-04-15 23:59:29 -07:00
parent 678c46b15e
commit 967646572c
2 changed files with 92 additions and 95 deletions

View File

@ -298,7 +298,7 @@ class ImageController extends GenericApiController
string $extension, string $extension,
array $state array $state
): Http\Response { ): Http\Response {
return Util::guardEx(function () use ($id, $name, $quality, $extension, $state) { return Util::guardEx(function () use ($id, $name, $width, $height, $quality, $extension, $state) {
// Get the file // Get the file
$file = $this->fs->getUserFile($id); $file = $this->fs->getUserFile($id);
@ -322,6 +322,11 @@ class ImageController extends GenericApiController
// Apply the edits // Apply the edits
(new Service\FileRobotMagick($image, $state))->apply(); (new Service\FileRobotMagick($image, $state))->apply();
// Resize the image
if ($width > 0 && $height > 0 && ($width !== $image->getImageWidth() || $height !== $image->getImageHeight())) {
$image->resizeImage($width, $height, \Imagick::FILTER_LANCZOS, 1, true);
}
// Save the image // Save the image
$image->setImageFormat($extension); $image->setImageFormat($extension);
$image->setImageCompressionQuality((int) round(100 * $quality)); $image->setImageCompressionQuality((int) round(100 * $quality));

View File

@ -8,9 +8,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from "vue"; import { defineComponent, PropType } from "vue";
import { basename, dirname, extname, join } from "path";
import { emit } from "@nextcloud/event-bus"; import { emit } from "@nextcloud/event-bus";
import { showError, showSuccess } from "@nextcloud/dialogs"; import { showError, showSuccess } from "@nextcloud/dialogs";
import axios from "@nextcloud/axios"; import axios from "@nextcloud/axios";
@ -20,6 +19,9 @@ import { FilerobotImageEditorConfig } from "react-filerobot-image-editor";
import translations from "./ImageEditorTranslations"; import translations from "./ImageEditorTranslations";
import { API } from "../../services/API"; import { API } from "../../services/API";
import { IPhoto } from "../../types";
import * as utils from "../../services/Utils";
import { fetchImage } from "../frame/XImgCache";
let TABS, TOOLS: any; let TABS, TOOLS: any;
type FilerobotImageEditor = import("filerobot-image-editor").default; type FilerobotImageEditor = import("filerobot-image-editor").default;
@ -36,16 +38,8 @@ async function loadFilerobot() {
export default defineComponent({ export default defineComponent({
props: { props: {
fileid: { photo: {
type: Number, type: Object as PropType<IPhoto>,
required: true,
},
src: {
type: String,
required: true,
},
etag: {
type: String,
required: true, required: true,
}, },
}, },
@ -57,10 +51,8 @@ export default defineComponent({
computed: { computed: {
config(): FilerobotImageEditorConfig & { theme: any } { config(): FilerobotImageEditorConfig & { theme: any } {
const src = API.IMAGE_DECODABLE(this.fileid, this.etag);
return { return {
source: src, source: "nonexistent",
defaultSavedImageName: this.defaultSavedImageName, defaultSavedImageName: this.defaultSavedImageName,
defaultSavedImageType: this.defaultSavedImageType, defaultSavedImageType: this.defaultSavedImageType,
@ -74,9 +66,9 @@ export default defineComponent({
defaultTabId: TABS.ADJUST, defaultTabId: TABS.ADJUST,
defaultToolId: TOOLS.CROP, defaultToolId: TOOLS.CROP,
// Displayed tabs, disabling watermark // Displayed tabs, disabling watermark and draw
tabsIds: Object.values(TABS) tabsIds: Object.values(TABS)
.filter((tab) => tab !== TABS.WATERMARK) .filter((tab) => ![TABS.WATERMARK, TABS.ANNOTATE].includes(tab))
.sort((a: string, b: string) => a.localeCompare(b)) as any[], .sort((a: string, b: string) => a.localeCompare(b)) as any[],
// onBeforeSave: this.onBeforeSave, // onBeforeSave: this.onBeforeSave,
@ -115,19 +107,20 @@ export default defineComponent({
}, },
}, },
savingPixelRatio: 8, savingPixelRatio: window.devicePixelRatio,
previewPixelRatio: window.devicePixelRatio, previewPixelRatio: window.devicePixelRatio,
}; };
}, },
defaultSavedImageName(): string { defaultSavedImageName(): string {
return basename(this.src, extname(this.src)); return this.photo.basename;
}, },
defaultSavedImageType(): "jpeg" | "png" | "webp" { defaultSavedImageType(): "jpeg" | "png" | "webp" {
const mime = extname(this.src).slice(1); if (
if (["jpeg", "png", "webp"].includes(mime)) { ["image/jpeg", "image/png", "image/webp"].includes(this.photo.mimetype)
return mime as any; ) {
return this.photo.mimetype.split("/")[1] as any;
} }
return "jpeg"; return "jpeg";
}, },
@ -151,32 +144,16 @@ export default defineComponent({
async mounted() { async mounted() {
await loadFilerobot(); await loadFilerobot();
this.imageEditor = new FilerobotImageEditor(
<any>this.$refs.editor, const div = <HTMLElement>this.$refs.editor;
<any>this.config const config = this.config;
); config.source = await this.getImg();
this.imageEditor = new FilerobotImageEditor(div, this.config);
this.imageEditor.render(); this.imageEditor.render();
// Handle keyboard
window.addEventListener("keydown", this.handleKeydown, true); window.addEventListener("keydown", this.handleKeydown, true);
// Get latest exif data
try {
const res = await axios.get(
API.Q(API.IMAGE_INFO(this.fileid), {
basic: "1",
current: "1",
})
);
this.exif = res.data?.current;
if (!this.exif) {
throw new Error("No exif data");
}
} catch (err) {
console.error(err);
alert(
this.t("memories", "Failed to get Exif data. Metadata may be lost!")
);
}
}, },
beforeDestroy() { beforeDestroy() {
@ -187,6 +164,27 @@ export default defineComponent({
}, },
methods: { methods: {
async getImg(): Promise<any> {
if (this.photo.w && this.photo.h) {
// Fetch the image to a blob
const preview = utils.getPreviewUrl(this.photo, false, 2048);
// Fetch preview image
const img = new Image();
img.height = this.photo.h;
img.width = this.photo.w;
await new Promise(async (resolve) => {
img.onload = resolve;
img.src = await fetchImage(preview);
});
return img;
}
// If we don't have the size, we need to use the original image
return API.IMAGE_DECODABLE(this.photo.fileid, this.photo.etag);
},
onClose(closingReason, haveNotSavedChanges) { onClose(closingReason, haveNotSavedChanges) {
if (haveNotSavedChanges) { if (haveNotSavedChanges) {
this.onExitWithoutSaving(); this.onExitWithoutSaving();
@ -201,67 +199,61 @@ export default defineComponent({
* *
* @see https://github.com/scaleflex/filerobot-image-editor#onsave * @see https://github.com/scaleflex/filerobot-image-editor#onsave
*/ */
async onSave({ async onSave(
fullName, data: {
imageBase64, name?: string;
}: { width?: number;
fullName?: string; height?: number;
imageBase64?: string; quality?: number;
}): Promise<void> { extension?: string;
if (!imageBase64) { fullName?: string;
throw new Error("No image data"); imageBase64?: string;
} },
state: any
): Promise<void> {
// Copy state
state = JSON.parse(JSON.stringify(state));
const { origin, pathname } = new URL(this.src); // Convert crop to relative values
const putUrl = origin + join(dirname(pathname), fullName); if (state?.adjustments?.crop) {
const iw = state.shownImageDimensions.width;
if ( const ih = state.shownImageDimensions.height;
!this.exif && const { x, y, width, height } = state.adjustments.crop;
!confirm(this.t("memories", "No Exif data found! Continue?")) state.adjustments.crop = {
) { x: x / iw,
return; y: y / ih,
width: width / iw,
height: height / ih,
};
} }
try { try {
const blob = await fetch(imageBase64).then((res) => res.blob()); const res = await axios.put(API.IMAGE_EDIT(this.photo.fileid), {
const response = await axios.put(putUrl, new File([blob], fullName)); name: data.name,
const fileid = width: data.width,
parseInt(response?.headers?.["oc-fileid"]?.split("oc")[0]) || null; height: data.height,
if (response.status >= 400) { quality: data.quality,
throw new Error("Failed to save image"); extension: data.extension,
} state: state,
// Strip old and incorrect exif data
const exif = this.exif;
delete exif.Orientation;
delete exif.Rotation;
delete exif.ImageHeight;
delete exif.ImageWidth;
delete exif.ImageSize;
delete exif.ModifyDate;
delete exif.ExifImageHeight;
delete exif.ExifImageWidth;
delete exif.ExifImageSize;
delete exif.CompatibleBrands;
delete exif.FileType;
delete exif.FileTypeExtension;
delete exif.MIMEType;
delete exif.MajorBrand;
// Update exif data
await axios.patch(API.IMAGE_SETEXIF(fileid), {
raw: exif,
}); });
const fileid = res.data.fileid;
// Success, emit an appropriate event
showSuccess(this.t("memories", "Image saved successfully")); showSuccess(this.t("memories", "Image saved successfully"));
if (fileid !== this.fileid) {
console.log(state);
if (fileid !== this.photo.fileid) {
console.log("Fileid changed", fileid);
emit("files:file:created", { fileid }); emit("files:file:created", { fileid });
} else { } else {
console.log("Fileid unchanged", fileid);
emit("files:file:updated", { fileid }); emit("files:file:updated", { fileid });
} }
this.onClose(undefined, false); this.onClose(undefined, false);
} catch (error) { } catch (err) {
showError(this.t("memories", "Error saving image")); showError(this.t("memories", "Error saving image"));
console.error(err);
} }
}, },