feat:Support compress image view

This commit is contained in:
liwei 2020-06-06 18:55:01 +08:00
parent b8300b7121
commit 8af40d28a7
7 changed files with 346 additions and 165 deletions

View File

@ -1,19 +1,23 @@
<template> <template>
<div class="item" <div
role="button" class="item"
tabindex="0" role="button"
draggable="true" tabindex="0"
@dragstart="dragStart" draggable="true"
@dragover="dragOver" @dragstart="dragStart"
@drop="drop" @dragover="dragOver"
@click="click" @drop="drop"
@dblclick="open" @click="click"
@touchstart="touchstart" @dblclick="open"
:data-dir="isDir" @touchstart="touchstart"
:aria-label="name" :data-dir="isDir"
:aria-selected="isSelected"> :aria-label="name"
:aria-selected="isSelected"
>
<div> <div>
<i class="material-icons">{{ icon }}</i> <img v-if="type==='image'" :src="compressUrl" />
<i v-else class="material-icons">{{ icon }}</i>
<!-- <i class="material-icons">{{ icon }}</i> -->
</div> </div>
<div> <div>
@ -30,140 +34,146 @@
</template> </template>
<script> <script>
import { mapMutations, mapGetters, mapState } from 'vuex' import { mapMutations, mapGetters, mapState } from "vuex";
import filesize from 'filesize' import { baseURL } from "@/utils/constants";
import moment from 'moment' import filesize from "filesize";
import { files as api } from '@/api' import moment from "moment";
import { files as api } from "@/api";
export default { export default {
name: 'item', name: "item",
data: function () { data: function() {
return { return {
touches: 0 touches: 0
} };
}, },
props: ['name', 'isDir', 'url', 'type', 'size', 'modified', 'index'], props: ["name", "isDir", "url", "type", "size", "modified", "index"],
computed: { computed: {
...mapState(['selected', 'req']), ...mapState(["selected", "req", "jwt"]),
...mapGetters(['selectedCount']), ...mapGetters(["selectedCount"]),
isSelected () { isSelected() {
return (this.selected.indexOf(this.index) !== -1) return this.selected.indexOf(this.index) !== -1;
}, },
icon () { icon() {
if (this.isDir) return 'folder' if (this.isDir) return "folder";
if (this.type === 'image') return 'insert_photo' if (this.type === "image") return "insert_photo";
if (this.type === 'audio') return 'volume_up' if (this.type === "audio") return "volume_up";
if (this.type === 'video') return 'movie' if (this.type === "video") return "movie";
return 'insert_drive_file' return "insert_drive_file";
}, },
canDrop () { canDrop() {
if (!this.isDir) return false if (!this.isDir) return false;
for (let i of this.selected) { for (let i of this.selected) {
if (this.req.items[i].url === this.url) { if (this.req.items[i].url === this.url) {
return false return false;
} }
} }
return true return true;
},
compressUrl() {
const path = this.url.replace(/^\/files\//, "");
return `${baseURL}/api/compress/${path}?auth=${this.jwt}&inline=true`;
} }
}, },
methods: { methods: {
...mapMutations(['addSelected', 'removeSelected', 'resetSelected']), ...mapMutations(["addSelected", "removeSelected", "resetSelected"]),
humanSize: function () { humanSize: function() {
return filesize(this.size) return filesize(this.size);
}, },
humanTime: function () { humanTime: function() {
return moment(this.modified).fromNow() return moment(this.modified).fromNow();
}, },
dragStart: function () { dragStart: function() {
if (this.selectedCount === 0) { if (this.selectedCount === 0) {
this.addSelected(this.index) this.addSelected(this.index);
return return;
} }
if (!this.isSelected) { if (!this.isSelected) {
this.resetSelected() this.resetSelected();
this.addSelected(this.index) this.addSelected(this.index);
} }
}, },
dragOver: function (event) { dragOver: function(event) {
if (!this.canDrop) return if (!this.canDrop) return;
event.preventDefault() event.preventDefault();
let el = event.target let el = event.target;
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
if (!el.classList.contains('item')) { if (!el.classList.contains("item")) {
el = el.parentElement el = el.parentElement;
} }
} }
el.style.opacity = 1 el.style.opacity = 1;
}, },
drop: function (event) { drop: function(event) {
if (!this.canDrop) return if (!this.canDrop) return;
event.preventDefault() event.preventDefault();
if (this.selectedCount === 0) return if (this.selectedCount === 0) return;
let items = [] let items = [];
for (let i of this.selected) { for (let i of this.selected) {
items.push({ items.push({
from: this.req.items[i].url, from: this.req.items[i].url,
to: this.url + this.req.items[i].name to: this.url + this.req.items[i].name
}) });
} }
api.move(items) api
.move(items)
.then(() => { .then(() => {
this.$store.commit('setReload', true) this.$store.commit("setReload", true);
}) })
.catch(this.$showError) .catch(this.$showError);
}, },
click: function (event) { click: function(event) {
if (this.selectedCount !== 0) event.preventDefault() if (this.selectedCount !== 0) event.preventDefault();
if (this.$store.state.selected.indexOf(this.index) !== -1) { if (this.$store.state.selected.indexOf(this.index) !== -1) {
this.removeSelected(this.index) this.removeSelected(this.index);
return return;
} }
if (event.shiftKey) { if (event.shiftKey) {
let fi = 0 let fi = 0;
let la = 0 let la = 0;
if (this.index > this.selected[0]) { if (this.index > this.selected[0]) {
fi = this.selected[0] + 1 fi = this.selected[0] + 1;
la = this.index la = this.index;
} else { } else {
fi = this.index fi = this.index;
la = this.selected[0] - 1 la = this.selected[0] - 1;
} }
for (; fi <= la; fi++) { for (; fi <= la; fi++) {
this.addSelected(fi) this.addSelected(fi);
} }
return return;
} }
if (!event.ctrlKey && !this.$store.state.multiple) this.resetSelected() if (!event.ctrlKey && !this.$store.state.multiple) this.resetSelected();
this.addSelected(this.index) this.addSelected(this.index);
}, },
touchstart () { touchstart() {
setTimeout(() => { setTimeout(() => {
this.touches = 0 this.touches = 0;
}, 300) }, 300);
this.touches++ this.touches++;
if (this.touches > 1) { if (this.touches > 1) {
this.open() this.open();
} }
}, },
open: function () { open: function() {
this.$router.push({path: this.url}) this.$router.push({ path: this.url });
} }
} }
} };
</script> </script>

View File

@ -1,7 +1,13 @@
<template> <template>
<div id="previewer"> <div id="previewer">
<div class="bar"> <div class="bar">
<button @click="back" class="action" :title="$t('files.closePreview')" :aria-label="$t('files.closePreview')" id="close"> <button
@click="back"
class="action"
:title="$t('files.closePreview')"
:aria-label="$t('files.closePreview')"
id="close"
>
<i class="material-icons">close</i> <i class="material-icons">close</i>
</button> </button>
@ -11,10 +17,22 @@
<info-button></info-button> <info-button></info-button>
</div> </div>
<button class="action" @click="prev" v-show="hasPrevious" :aria-label="$t('buttons.previous')" :title="$t('buttons.previous')"> <button
class="action"
@click="prev"
v-show="hasPrevious"
:aria-label="$t('buttons.previous')"
:title="$t('buttons.previous')"
>
<i class="material-icons">chevron_left</i> <i class="material-icons">chevron_left</i>
</button> </button>
<button class="action" @click="next" v-show="hasNext" :aria-label="$t('buttons.next')" :title="$t('buttons.next')"> <button
class="action"
@click="next"
v-show="hasNext"
:aria-label="$t('buttons.next')"
:title="$t('buttons.next')"
>
<i class="material-icons">chevron_right</i> <i class="material-icons">chevron_right</i>
</button> </button>
@ -27,39 +45,39 @@
v-for="(sub, index) in subtitles" v-for="(sub, index) in subtitles"
:key="index" :key="index"
:src="sub" :src="sub"
:label="'Subtitle ' + index" :default="index === 0"> :label="'Subtitle ' + index"
Sorry, your browser doesn't support embedded videos, :default="index === 0"
but don't worry, you can <a :href="download">download it</a> />Sorry, your browser doesn't support embedded videos,
but don't worry, you can
<a :href="download">download it</a>
and watch it with your favorite video player! and watch it with your favorite video player!
</video> </video>
<object v-else-if="req.extension == '.pdf'" class="pdf" :data="raw"></object> <object v-else-if="req.extension == '.pdf'" class="pdf" :data="raw"></object>
<a v-else-if="req.type == 'blob'" :href="download"> <a v-else-if="req.type == 'blob'" :href="download">
<h2 class="message">{{ $t('buttons.download') }} <i class="material-icons">file_download</i></h2> <h2 class="message">
{{ $t('buttons.download') }}
<i class="material-icons">file_download</i>
</h2>
</a> </a>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex' import { mapState } from "vuex";
import url from '@/utils/url' import url from "@/utils/url";
import { baseURL } from '@/utils/constants' import { baseURL } from "@/utils/constants";
import { files as api } from '@/api' import { files as api } from "@/api";
import InfoButton from '@/components/buttons/Info' import InfoButton from "@/components/buttons/Info";
import DeleteButton from '@/components/buttons/Delete' import DeleteButton from "@/components/buttons/Delete";
import RenameButton from '@/components/buttons/Rename' import RenameButton from "@/components/buttons/Rename";
import DownloadButton from '@/components/buttons/Download' import DownloadButton from "@/components/buttons/Download";
import ExtendedImage from './ExtendedImage' import ExtendedImage from "./ExtendedImage";
const mediaTypes = [ const mediaTypes = ["image", "video", "audio", "blob"];
"image",
"video",
"audio",
"blob"
]
export default { export default {
name: 'preview', name: "preview",
components: { components: {
InfoButton, InfoButton,
DeleteButton, DeleteButton,
@ -67,94 +85,105 @@ export default {
DownloadButton, DownloadButton,
ExtendedImage ExtendedImage
}, },
data: function () { data: function() {
return { return {
previousLink: '', previousLink: "",
nextLink: '', nextLink: "",
listing: null, listing: null,
subtitles: [] subtitles: []
} };
}, },
computed: { computed: {
...mapState(['req', 'user', 'oldReq', 'jwt']), ...mapState(["req", "user", "oldReq", "jwt"]),
hasPrevious () { hasPrevious() {
return (this.previousLink !== '') return this.previousLink !== "";
}, },
hasNext () { hasNext() {
return (this.nextLink !== '') return this.nextLink !== "";
}, },
download () { download() {
return `${baseURL}/api/raw${this.req.path}?auth=${this.jwt}` return `${baseURL}/api/raw${this.req.path}?auth=${this.jwt}`;
}, },
raw () { compress() {
return `${this.download}&inline=true` if (this.type === 'image') {
return `${baseURL}/api/compress${this.req.path}?auth=${this.jwt}`;
} else {
return `${baseURL}/api/raw${this.req.path}?auth=${this.jwt}`;
}
},
raw() {
return `${this.compress}&inline=true`;
} }
}, },
async mounted () { async mounted() {
window.addEventListener('keyup', this.key) window.addEventListener("keyup", this.key);
if (this.req.subtitles) { if (this.req.subtitles) {
this.subtitles = this.req.subtitles.map(sub => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`) this.subtitles = this.req.subtitles.map(
sub => `${baseURL}/api/raw${sub}?auth=${this.jwt}&inline=true`
);
} }
try { try {
if (this.oldReq.items) { if (this.oldReq.items) {
this.updateLinks(this.oldReq.items) this.updateLinks(this.oldReq.items);
} else { } else {
const path = url.removeLastDir(this.$route.path) const path = url.removeLastDir(this.$route.path);
const res = await api.fetch(path) const res = await api.fetch(path);
this.updateLinks(res.items) this.updateLinks(res.items);
} }
} catch (e) { } catch (e) {
this.$showError(e) this.$showError(e);
} }
}, },
beforeDestroy () { beforeDestroy() {
window.removeEventListener('keyup', this.key) window.removeEventListener("keyup", this.key);
}, },
methods: { methods: {
back () { back() {
let uri = url.removeLastDir(this.$route.path) + '/' let uri = url.removeLastDir(this.$route.path) + "/";
this.$router.push({ path: uri }) this.$router.push({ path: uri });
}, },
prev () { prev() {
this.$router.push({ path: this.previousLink }) this.$router.push({ path: this.previousLink });
}, },
next () { next() {
this.$router.push({ path: this.nextLink }) this.$router.push({ path: this.nextLink });
}, },
key (event) { key(event) {
event.preventDefault() event.preventDefault();
if (event.which === 13 || event.which === 39) { // right arrow if (event.which === 13 || event.which === 39) {
if (this.hasNext) this.next() // right arrow
} else if (event.which === 37) { // left arrow if (this.hasNext) this.next();
if (this.hasPrevious) this.prev() } else if (event.which === 37) {
// left arrow
if (this.hasPrevious) this.prev();
} }
}, },
updateLinks (items) { updateLinks(items) {
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
if (items[i].name !== this.req.name) { if (items[i].name !== this.req.name) {
continue continue;
} }
for (let j = i - 1; j >= 0; j--) { for (let j = i - 1; j >= 0; j--) {
if (mediaTypes.includes(items[j].type)) { if (mediaTypes.includes(items[j].type)) {
this.previousLink = items[j].url this.previousLink = items[j].url;
break break;
} }
} }
for (let j = i + 1; j < items.length; j++) { for (let j = i + 1; j < items.length; j++) {
if (mediaTypes.includes(items[j].type)) { if (mediaTypes.includes(items[j].type)) {
this.nextLink = items[j].url this.nextLink = items[j].url;
break break;
} }
} }
return return;
} }
} }
} }
} };
</script> </script>

View File

@ -1,6 +1,6 @@
#listing h2 { #listing h2 {
margin: 0 0 0 0.5em; margin: 0 0 0 0.5em;
font-size: .9em; font-size: 0.9em;
color: rgba(0, 0, 0, 0.38); color: rgba(0, 0, 0, 0.38);
font-weight: 500; font-weight: 500;
} }
@ -10,7 +10,7 @@
overflow: hidden; overflow: hidden;
} }
#listing>div { #listing > div {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: flex-start; justify-content: flex-start;
@ -22,7 +22,7 @@
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
color: #6f6f6f; color: #6f6f6f;
transition: .1s ease background, .1s ease opacity; transition: 0.1s ease background, 0.1s ease opacity;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
} }
@ -52,6 +52,13 @@
vertical-align: bottom; vertical-align: bottom;
} }
#listing .item img {
width: 4em;
height: 4em;
margin-right: 0.1em;
vertical-align: bottom;
}
.message { .message {
text-align: center; text-align: center;
font-size: 2em; font-size: 2em;
@ -64,7 +71,7 @@
.message i { .message i {
font-size: 2.5em; font-size: 2.5em;
margin-bottom: .2em; margin-bottom: 0.2em;
display: block; display: block;
} }
@ -75,14 +82,14 @@
#listing.mosaic .item { #listing.mosaic .item {
width: calc(33% - 1em); width: calc(33% - 1em);
margin: .5em; margin: 0.5em;
padding: 0.5em; padding: 0.5em;
border-radius: 0.2em; border-radius: 0.2em;
box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .12); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.12);
} }
#listing.mosaic .item:hover { #listing.mosaic .item:hover {
box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .24) !important; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24) !important;
} }
#listing.mosaic .header { #listing.mosaic .header {
@ -116,7 +123,7 @@
display: none; display: none;
} }
#listing .item[aria-selected=true] { #listing .item[aria-selected="true"] {
background: var(--blue) !important; background: var(--blue) !important;
color: #fff !important; color: #fff !important;
} }
@ -129,6 +136,11 @@
font-size: 2em; font-size: 2em;
} }
#listing.list .item div:first-of-type img {
width: 2em;
height: 2em;
}
#listing.list .item div:last-of-type { #listing.list .item div:last-of-type {
width: calc(100% - 3em); width: calc(100% - 3em);
display: flex; display: flex;
@ -151,19 +163,19 @@
#listing.list .header i { #listing.list .header i {
font-size: 1.5em; font-size: 1.5em;
vertical-align: middle; vertical-align: middle;
margin-left: .2em; margin-left: 0.2em;
} }
#listing.list .item.header { #listing.list .item.header {
display: flex !important; display: flex !important;
background: #fafafa; background: #fafafa;
z-index: 999; z-index: 999;
padding: .85em; padding: 0.85em;
border: 0; border: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1); border-bottom: 1px solid rgba(0, 0, 0, 0.1);
} }
#listing.list .item.header>div:first-child { #listing.list .item.header > div:first-child {
width: 0; width: 0;
} }
@ -175,7 +187,7 @@
color: inherit; color: inherit;
} }
#listing.list .item.header>div:first-child { #listing.list .item.header > div:first-child {
width: 0; width: 0;
} }
@ -193,7 +205,7 @@
#listing.list .header i { #listing.list .header i {
opacity: 0; opacity: 0;
transition: .1s ease all; transition: 0.1s ease all;
} }
#listing.list .header p:hover i, #listing.list .header p:hover i,
@ -215,7 +227,7 @@
height: 4em; height: 4em;
padding: 0.5em 0.5em 0.5em 1em; padding: 0.5em 0.5em 0.5em 1em;
justify-content: space-between; justify-content: space-between;
transition: .2s ease bottom; transition: 0.2s ease bottom;
} }
#listing #multiple-selection.active { #listing #multiple-selection.active {

1
go.mod
View File

@ -16,6 +16,7 @@ require (
github.com/maruel/natural v0.0.0-20180416170133-dbcb3e2e8cf1 github.com/maruel/natural v0.0.0-20180416170133-dbcb3e2e8cf1
github.com/mholt/archiver v3.1.1+incompatible github.com/mholt/archiver v3.1.1+incompatible
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/nwaples/rardecode v1.0.0 // indirect github.com/nwaples/rardecode v1.0.0 // indirect
github.com/pelletier/go-toml v1.6.0 github.com/pelletier/go-toml v1.6.0
github.com/pierrec/lz4 v0.0.0-20190131084431-473cd7ce01a1 // indirect github.com/pierrec/lz4 v0.0.0-20190131084431-473cd7ce01a1 // indirect

2
go.sum
View File

@ -141,6 +141,8 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=

View File

@ -59,6 +59,7 @@ func NewHandler(store *storage.Storage, server *settings.Server) (http.Handler,
api.Handle("/settings", monkey(settingsPutHandler, "")).Methods("PUT") api.Handle("/settings", monkey(settingsPutHandler, "")).Methods("PUT")
api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET") api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET")
api.PathPrefix("/compress").Handler(monkey(compressHandler, "/api/compress")).Methods("GET")
api.PathPrefix("/command").Handler(monkey(commandsHandler, "/api/command")).Methods("GET") api.PathPrefix("/command").Handler(monkey(commandsHandler, "/api/command")).Methods("GET")
api.PathPrefix("/search").Handler(monkey(searchHandler, "/api/search")).Methods("GET") api.PathPrefix("/search").Handler(monkey(searchHandler, "/api/search")).Methods("GET")

126
http/preview.go Normal file
View File

@ -0,0 +1,126 @@
package http
import (
"bytes"
"errors"
"fmt"
"github.com/filebrowser/filebrowser/v2/files"
"github.com/nfnt/resize"
"image"
"image/gif"
"image/jpeg"
"image/png"
"io"
"mime"
"net/http"
"net/url"
)
var compressHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if !d.user.Perm.Download {
return http.StatusAccepted, nil
}
file, err := files.NewFileInfo(files.FileOptions{
Fs: d.user.Fs,
Path: r.URL.Path,
Modify: d.user.Perm.Modify,
Expand: true,
Checker: d,
})
if err != nil {
return errToStatus(err), err
}
if file.IsDir || file.Type != "image" {
return http.StatusNotFound, nil
}
return compressFileHandler(w, r, file)
})
func compressFileHandler(w http.ResponseWriter, r *http.Request, file *files.FileInfo) (int, error) {
fd, err := file.Fs.Open(file.Path)
if err != nil {
return http.StatusInternalServerError, err
}
defer fd.Close()
if r.URL.Query().Get("inline") == "true" {
w.Header().Set("Content-Disposition", "inline")
} else {
// As per RFC6266 section 4.3
w.Header().Set("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(file.Name))
}
buf, err := compressImageHandler(file, fd)
if err != nil {
return errToStatus(err), err
}
w.Header().Add("Content-Length", fmt.Sprintf("%d", buf.Len()))
w.Header().Add("Content-Type", mime.TypeByExtension(file.Extension))
io.Copy(w, buf)
return 0, nil
}
func compressImageHandler(file *files.FileInfo, fd io.Reader) (*bytes.Buffer, error) {
var (
buf *bytes.Buffer
m image.Image
err error
)
switch file.Extension {
case ".jpg", ".jpeg":
buf, m, err = compressImage(jpeg.Decode, fd)
if err != nil {
return nil, err
}
err = jpeg.Encode(buf, m, nil)
break
case ".png":
buf, m, err = compressImage(png.Decode, fd)
if err != nil {
return nil, err
}
err = png.Encode(buf, m)
break
case ".gif":
buf, m, err = compressImage(gif.Decode, fd)
if err != nil {
return nil, err
}
err = gif.Encode(buf, m, nil)
break
default:
return nil, errors.New("extension is not supported")
}
if err != nil {
return nil, err
}
return buf, nil
}
const maxSize = 1080
func compressImage(decode func(r io.Reader) (image.Image, error), fd io.Reader) (*bytes.Buffer, image.Image, error) {
img, err := decode(fd)
if err != nil {
return nil, nil, err
}
buf := bytes.NewBuffer([]byte{})
width := img.Bounds().Dx()
height := img.Bounds().Dy()
if width > maxSize && width > height {
width = maxSize
height = 0
} else if height > maxSize && height > width {
width = 0
height = maxSize
} else {
width = 0
height = 0
}
m := resize.Resize(uint(width), uint(height), img, resize.Lanczos3)
return buf, m, nil
}