mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 08:22:24 +01:00
feature (plg_image_thumbnail): improvement over thumbnailing
This commit is contained in:
parent
a78e817657
commit
339e9486e5
35 changed files with 823 additions and 364 deletions
11
Makefile
11
Makefile
|
|
@ -1,12 +1,17 @@
|
|||
all:
|
||||
make build_backend
|
||||
make build_init
|
||||
make build_frontend
|
||||
GOARCH=amd64 GOOS=linux make build_backend
|
||||
# GOARCH=arm GOARM=7 GOOS=linux make build_backend
|
||||
|
||||
build_init:
|
||||
find server/plugin/plg_* -type f -name "install.sh" -exec {} \;
|
||||
go generate -x ./server/...
|
||||
|
||||
build_frontend:
|
||||
NODE_ENV=production npm run build
|
||||
|
||||
build_backend:
|
||||
PKG_CONFIG_PATH=/usr/local/lib/pkgconfig/ CGO_CFLAGS_ALLOW='-fopenmp' go build -mod=vendor --tags "fts5" -ldflags "-X github.com/mickael-kerjean/filestash/server/common.BUILD_DATE=`date -u +%Y%m%d` -X github.com/mickael-kerjean/filestash/server/common.BUILD_REF=`git rev-parse HEAD`" -o dist/filestash server/main.go
|
||||
CGO_ENABLED=0 go build -ldflags="-extldflags=-static" -mod=vendor --tags "fts5" -o dist/filestash server/main.go
|
||||
|
||||
clean_frontend:
|
||||
rm -rf server/ctrl/static/www/
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
"crw": "image/x-canon-crw",
|
||||
"css": "text/css",
|
||||
"csv": "text/csv",
|
||||
"dcm": "image/dicom",
|
||||
"dcr": "image/x-kodak-dcr",
|
||||
"deb": "application/octet-stream",
|
||||
"der": "application/x-x509-ca-cert",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,38 @@
|
|||
PLATFORM=linux
|
||||
ARCH=amd64
|
||||
|
||||
all:
|
||||
@make jpeg
|
||||
@make png
|
||||
@make raw
|
||||
|
||||
jpeg:
|
||||
gcc -Wall src/jpeg_to_jpeg.c -o dist/jpeg.bin -l:libjpeg.a
|
||||
image_jpeg.o:
|
||||
gcc -Wall -c src/image_jpeg.c -o src/image_jpeg.o
|
||||
|
||||
png:
|
||||
gcc -Wall src/png_to_webp.c -o dist/png.bin -l:libpng.a -l:libz.a -l:libwebp.a -lpthread -lm
|
||||
image_png.o:
|
||||
gcc -Wall -c src/image_png.c -o src/image_png.o
|
||||
|
||||
image_raw.o:
|
||||
gcc -Wall -c src/image_raw.c -o src/image_raw.o
|
||||
|
||||
jpeg: image_jpeg.o
|
||||
gcc -static src/main_jpeg_to_jpeg.c src/image_jpeg.o -o dist/jpeg_$(PLATFORM)_$(ARCH).bin -l:libjpeg.a
|
||||
sha256sum ./dist/jpeg_$(PLATFORM)_$(ARCH).bin | awk '{ printf $$1 }' > dist/jpeg_$(PLATFORM)_$(ARCH).bin.sha256
|
||||
|
||||
png: image_png.o
|
||||
gcc -static -Wall src/main_png_to_webp.c src/image_png.o -o dist/png_$(PLATFORM)_$(ARCH).bin -l:libpng.a -l:libz.a -l:libwebp.a -lpthread -lm
|
||||
sha256sum ./dist/png_$(PLATFORM)_$(ARCH).bin | awk '{ printf $$1 }' > dist/png_$(PLATFORM)_$(ARCH).bin.sha256
|
||||
|
||||
raw: image_raw.o image_jpeg.o
|
||||
gcc -Wall -c src/main_raw_to_jpeg.c -o src/main_raw_to_jpeg.o
|
||||
# libraw was configured like this: ./configure --disable-openmp --disable-lcms
|
||||
g++ -static src/main_raw_to_jpeg.o src/image_raw.o src/image_jpeg.o -o dist/raw_$(PLATFORM)_$(ARCH).bin -l:libraw.a -l:libjpeg.a -l:libz.a -l:libstdc++.a -lm
|
||||
sha256sum ./dist/raw_$(PLATFORM)_$(ARCH).bin | awk '{ printf $$1 }' > dist/raw_$(PLATFORM)_$(ARCH).bin.sha256
|
||||
|
||||
clean:
|
||||
rm src/*.o dist/*_$(PLATFORM)_$(ARCH).bin* || true
|
||||
|
||||
test:
|
||||
@gcc -Wall src/test.c -o test.bin -ljpeg -lpng -lz -lwebp -lpthread -lm -lraw
|
||||
@./test.bin /home/mickael/Downloads/
|
||||
@rm test.bin
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -9,6 +9,9 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed dist/placeholder.png
|
||||
|
|
@ -17,6 +20,16 @@ var placeholder []byte
|
|||
func init() {
|
||||
Hooks.Register.Thumbnailer("image/png", thumbnailBuilder{thumbnailPng})
|
||||
Hooks.Register.Thumbnailer("image/jpeg", thumbnailBuilder{thumbnailJpeg})
|
||||
for _, mType := range []string{
|
||||
"image/x-canon-cr2", "image/x-fuji-raf", "image/x-nikon-nef",
|
||||
"image/x-nikon-nrw", "image/x-epson-erf",
|
||||
// "image/tiff",
|
||||
// "image/x-kodak-dcr", "image/x-hasselblad-3fr",
|
||||
// "image/x-raw",
|
||||
} {
|
||||
Hooks.Register.Thumbnailer(mType, thumbnailBuilder{thumbnailRaw})
|
||||
}
|
||||
// TODO: Hooks.Register.ProcessFileContentBeforeSend for raw files rendering
|
||||
}
|
||||
|
||||
func thumbnailPng(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
|
||||
|
|
@ -35,6 +48,33 @@ func thumbnailPng(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req
|
|||
func thumbnailJpeg(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
|
||||
h := (*res).Header()
|
||||
r, err := createThumbnailForJpeg(reader)
|
||||
if err != nil {
|
||||
h.Set("Content-Type", "image/png")
|
||||
h.Set("Cache-Control", "max-age=1")
|
||||
return NewReadCloserFromBytes(placeholder), nil
|
||||
}
|
||||
h.Set("Content-Type", "image/jpeg")
|
||||
h.Set("Cache-Control", fmt.Sprintf("max-age=%d", 3600*12))
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func thumbnailRaw(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
|
||||
h := (*res).Header()
|
||||
r, err := createThumbnailForRaw(reader)
|
||||
if err != nil {
|
||||
h.Set("Content-Type", "image/png")
|
||||
h.Set("Cache-Control", "max-age=1")
|
||||
return NewReadCloserFromBytes(placeholder), nil
|
||||
}
|
||||
h.Set("Content-Type", "image/jpeg")
|
||||
h.Set("Cache-Control", fmt.Sprintf("max-age=%d", 3600*12))
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func renderRaw(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
|
||||
h := (*res).Header()
|
||||
r, err := createThumbnailForRaw(reader)
|
||||
// r, err := createExtractForRaw(reader)
|
||||
if err != nil {
|
||||
h.Set("Content-Type", "image/png")
|
||||
return NewReadCloserFromBytes(placeholder), nil
|
||||
|
|
@ -52,34 +92,67 @@ func (this thumbnailBuilder) Generate(reader io.ReadCloser, ctx *App, res *http.
|
|||
return this.fn(reader, ctx, res, req)
|
||||
}
|
||||
|
||||
func setupProgram(name string, raw []byte) error {
|
||||
p := "/tmp/" + name
|
||||
type ThumbnailExecutable struct {
|
||||
Name string
|
||||
Binary *[]byte
|
||||
Checksum []byte
|
||||
isValid bool
|
||||
lastVerify time.Time
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (this ThumbnailExecutable) Init() {
|
||||
p := "/tmp/" + this.Name
|
||||
f, err := os.OpenFile(p, os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
outFile, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
Log.Warning("plg_image_thumbnail::init::run::openFile '%s'", this.Name)
|
||||
return
|
||||
}
|
||||
outFile.Write(raw)
|
||||
outFile.Write(*this.Binary)
|
||||
if err = outFile.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err = os.OpenFile(p, os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
Log.Warning("plg_image_thumbnail::init::run::close '%s'", this.Name)
|
||||
return
|
||||
}
|
||||
}
|
||||
b := make([]byte, 5)
|
||||
n, err := f.Read(b)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
} else if n != 5 {
|
||||
f.Close()
|
||||
return errors.New("unexpected read")
|
||||
} else if bytes.Equal(b, raw[:5]) == false {
|
||||
f.Close()
|
||||
return errors.New("different data")
|
||||
if f.Close() != nil {
|
||||
Log.Warning("plg_image_thumbnail::init::close '%s'", this.Name)
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
func (this *ThumbnailExecutable) verify() bool {
|
||||
this.Lock()
|
||||
defer this.Unlock()
|
||||
if time.Since(this.lastVerify) > 30*time.Second {
|
||||
this.lastVerify = time.Now()
|
||||
f, err := os.OpenFile("/tmp/"+this.Name, os.O_RDONLY, os.ModePerm)
|
||||
if err == nil && bytes.Equal([]byte(HashStream(f, 0)), this.Checksum) {
|
||||
this.isValid = true
|
||||
}
|
||||
}
|
||||
return this.isValid
|
||||
}
|
||||
|
||||
func (this *ThumbnailExecutable) Execute(reader io.ReadCloser) (io.ReadCloser, error) {
|
||||
if this.verify() == false {
|
||||
Log.Error("plg_image_thumbnail::execution abort after verification on '%s'", this.Name)
|
||||
reader.Close()
|
||||
return nil, ErrFilesystemError
|
||||
}
|
||||
// TODO: rate limit this
|
||||
var buf bytes.Buffer
|
||||
var errBuff bytes.Buffer
|
||||
cmd := exec.Command("/tmp/" + this.Name)
|
||||
cmd.Stdin = reader
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &errBuff
|
||||
if err := cmd.Run(); err != nil {
|
||||
reader.Close()
|
||||
Log.Debug("plg_image_thumbmail::resize %s ERR %s", this.Name, string(errBuff.Bytes()))
|
||||
return nil, errors.New(string(errBuff.Bytes()))
|
||||
}
|
||||
cmd.Wait()
|
||||
reader.Close()
|
||||
return NewReadCloserFromBytes(buf.Bytes()), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,19 @@
|
|||
package plg_image_thumbnail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"io"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func createThumbnailForJpeg(reader io.ReadCloser) (io.ReadCloser, error) {
|
||||
name := "thumbnail_web.bin"
|
||||
err := setupProgram(name, binaryThumbnailJpeg)
|
||||
if err != nil {
|
||||
Log.Warning("plg_image_thumbnail %s")
|
||||
reader.Close()
|
||||
return nil, err
|
||||
var exeForJpeg ThumbnailExecutable = ThumbnailExecutable{
|
||||
Name: "thumbnail_jpeg.bin",
|
||||
Binary: &binaryThumbnailJpeg,
|
||||
Checksum: checksumJpeg,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
var errBuff bytes.Buffer
|
||||
cmd := exec.Command("/tmp/" + name)
|
||||
cmd.Stdin = reader
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &errBuff
|
||||
if err := cmd.Run(); err != nil {
|
||||
reader.Close()
|
||||
Log.Debug("plg_image_thumbmail::resize_jpeg ERR %s", string(errBuff.Bytes()))
|
||||
return nil, errors.New(string(errBuff.Bytes()))
|
||||
func init() {
|
||||
exeForJpeg.Init()
|
||||
}
|
||||
cmd.Wait()
|
||||
reader.Close()
|
||||
return NewReadCloserFromBytes(buf.Bytes()), nil
|
||||
|
||||
func createThumbnailForJpeg(reader io.ReadCloser) (io.ReadCloser, error) {
|
||||
return exeForJpeg.Execute(reader)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,3 +6,6 @@ import (
|
|||
|
||||
//go:embed dist/jpeg_linux_amd64.bin
|
||||
var binaryThumbnailJpeg []byte
|
||||
|
||||
//go:embed dist/jpeg_linux_amd64.bin.sha256
|
||||
var checksumJpeg []byte
|
||||
|
|
|
|||
|
|
@ -6,3 +6,6 @@ import (
|
|||
|
||||
//go:embed dist/jpeg_linux_arm.bin
|
||||
var binaryThumbnailJpeg []byte
|
||||
|
||||
//go:embed dist/jpeg_linux_arm.bin.sha256
|
||||
var checksumJpeg []byte
|
||||
|
|
|
|||
|
|
@ -1,35 +1,19 @@
|
|||
package plg_image_thumbnail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"errors"
|
||||
. "github.com/mickael-kerjean/filestash/server/common"
|
||||
"io"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func createThumbnailForPng(reader io.ReadCloser) (io.ReadCloser, error) {
|
||||
name := "thumbnail_webp.bin"
|
||||
err := setupProgram(name, binaryThumbnailPng)
|
||||
if err != nil {
|
||||
Log.Warning("plg_image_thumbnail %s")
|
||||
reader.Close()
|
||||
return nil, err
|
||||
var exeForPng ThumbnailExecutable = ThumbnailExecutable{
|
||||
Name: "thumbnail_png.bin",
|
||||
Binary: &binaryThumbnailPng,
|
||||
Checksum: checksumPng,
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
var errBuff bytes.Buffer
|
||||
cmd := exec.Command("/tmp/" + name)
|
||||
cmd.Stdin = reader
|
||||
cmd.Stdout = &buf
|
||||
cmd.Stderr = &errBuff
|
||||
if err := cmd.Run(); err != nil {
|
||||
reader.Close()
|
||||
Log.Debug("plg_image_thumbmail::resize_webp ERR %s", string(errBuff.Bytes()))
|
||||
return nil, errors.New(string(errBuff.Bytes()))
|
||||
func init() {
|
||||
exeForPng.Init()
|
||||
}
|
||||
cmd.Wait()
|
||||
reader.Close()
|
||||
return NewReadCloserFromBytes(buf.Bytes()), nil
|
||||
|
||||
func createThumbnailForPng(reader io.ReadCloser) (io.ReadCloser, error) {
|
||||
return exeForPng.Execute(reader)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,3 +6,6 @@ import (
|
|||
|
||||
//go:embed dist/png_linux_amd64.bin
|
||||
var binaryThumbnailPng []byte
|
||||
|
||||
//go:embed dist/png_linux_amd64.bin.sha256
|
||||
var checksumPng []byte
|
||||
|
|
|
|||
|
|
@ -6,3 +6,6 @@ import (
|
|||
|
||||
//go:embed dist/png_linux_arm.bin
|
||||
var binaryThumbnailPng []byte
|
||||
|
||||
//go:embed dist/png_linux_arm.bin.sha256
|
||||
var checksumPng []byte
|
||||
|
|
|
|||
50
server/plugin/plg_image_thumbnail/resize_raw.go
Normal file
50
server/plugin/plg_image_thumbnail/resize_raw.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package plg_image_thumbnail
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
var exeForRaw ThumbnailExecutable = ThumbnailExecutable{
|
||||
Name: "thumbnail_raw.bin",
|
||||
Binary: &binaryThumbnailRaw,
|
||||
Checksum: checksumRaw,
|
||||
}
|
||||
|
||||
func init() {
|
||||
exeForRaw.Init()
|
||||
}
|
||||
|
||||
func createThumbnailForRaw(reader io.ReadCloser) (io.ReadCloser, error) {
|
||||
return exeForRaw.Execute(reader)
|
||||
}
|
||||
|
||||
func isRaw(mType string) bool {
|
||||
switch mType {
|
||||
case "image/x-tif":
|
||||
case "image/x-canon-cr2":
|
||||
case "image/x-canon-crw":
|
||||
case "image/x-nikon-nef":
|
||||
case "image/x-nikon-nrw":
|
||||
case "image/x-sony-arw":
|
||||
case "image/x-sony-sr2":
|
||||
case "image/x-minolta-mrw":
|
||||
case "image/x-minolta-mdc":
|
||||
case "image/x-olympus-orf":
|
||||
case "image/x-panasonic-rw2":
|
||||
case "image/x-pentax-pef":
|
||||
case "image/x-epson-erf":
|
||||
case "image/x-raw":
|
||||
case "image/x-x3f":
|
||||
case "image/x-fuji-raf":
|
||||
case "image/x-aptus-mos":
|
||||
case "image/x-mamiya-mef":
|
||||
case "image/x-hasselblad-3fr":
|
||||
case "image/x-adobe-dng":
|
||||
case "image/x-samsung-srw":
|
||||
case "image/x-kodak-kdc":
|
||||
case "image/x-kodak-dcr":
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
11
server/plugin/plg_image_thumbnail/resize_raw_linux_amd64.go
Normal file
11
server/plugin/plg_image_thumbnail/resize_raw_linux_amd64.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package plg_image_thumbnail
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed dist/raw_linux_amd64.bin
|
||||
var binaryThumbnailRaw []byte
|
||||
|
||||
//go:embed dist/raw_linux_amd64.bin.sha256
|
||||
var checksumRaw []byte
|
||||
11
server/plugin/plg_image_thumbnail/resize_raw_linux_arm.go
Normal file
11
server/plugin/plg_image_thumbnail/resize_raw_linux_arm.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package plg_image_thumbnail
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed dist/raw_linux_arm.bin
|
||||
var binaryThumbnailRaw []byte
|
||||
|
||||
//go:embed dist/raw_linux_arm.bin.sha256
|
||||
var checksumRaw []byte
|
||||
217
server/plugin/plg_image_thumbnail/src/image_jpeg.c
Normal file
217
server/plugin/plg_image_thumbnail/src/image_jpeg.c
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
#include <stdio.h>
|
||||
#include "utils.h"
|
||||
#include "jpeglib.h"
|
||||
// #include "webp/encode.h"
|
||||
|
||||
#define JPEG_QUALITY 50
|
||||
|
||||
#define min(a, b) (a > b ? b : a)
|
||||
|
||||
int jpeg_to_jpeg(FILE* input, FILE* output) {
|
||||
#ifdef HAS_DEBUG
|
||||
clock_t t;
|
||||
t = clock();
|
||||
#endif
|
||||
|
||||
struct jpeg_decompress_struct jpeg_config_input;
|
||||
struct jpeg_compress_struct jpeg_config_output;
|
||||
struct jpeg_error_mgr jerr;
|
||||
int jpeg_row_stride;
|
||||
int image_min_size;
|
||||
JSAMPARRAY buffer;
|
||||
|
||||
jpeg_config_input.err = jpeg_std_error(&jerr);
|
||||
jpeg_config_output.err = jpeg_std_error(&jerr);
|
||||
jpeg_config_input.dct_method = JDCT_IFAST;
|
||||
jpeg_config_input.do_fancy_upsampling = FALSE;
|
||||
jpeg_config_input.two_pass_quantize = FALSE;
|
||||
jpeg_config_input.dither_mode = JDITHER_ORDERED;
|
||||
|
||||
jpeg_create_decompress(&jpeg_config_input);
|
||||
jpeg_create_compress(&jpeg_config_output);
|
||||
jpeg_stdio_src(&jpeg_config_input, input);
|
||||
jpeg_stdio_dest(&jpeg_config_output, output);
|
||||
DEBUG("after constructor decompress");
|
||||
if(jpeg_read_header(&jpeg_config_input, TRUE) != JPEG_HEADER_OK) {
|
||||
jpeg_destroy_decompress(&jpeg_config_input);
|
||||
return 1;
|
||||
}
|
||||
DEBUG("after header read");
|
||||
jpeg_config_input.dct_method = JDCT_IFAST;
|
||||
jpeg_config_input.do_fancy_upsampling = FALSE;
|
||||
jpeg_config_input.two_pass_quantize = FALSE;
|
||||
jpeg_config_input.dither_mode = JDITHER_ORDERED;
|
||||
jpeg_calc_output_dimensions(&jpeg_config_input);
|
||||
|
||||
image_min_size = min(jpeg_config_input.output_width, jpeg_config_input.output_height);
|
||||
jpeg_config_input.scale_num = 1;
|
||||
jpeg_config_input.scale_denom = 1;
|
||||
if (image_min_size / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 1;
|
||||
jpeg_config_input.scale_denom = 8;
|
||||
} else if (image_min_size * 2 / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 1;
|
||||
jpeg_config_input.scale_denom = 4;
|
||||
} else if (image_min_size * 3 / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 3;
|
||||
jpeg_config_input.scale_denom = 8;
|
||||
} else if (image_min_size * 4 / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 4;
|
||||
jpeg_config_input.scale_denom = 8;
|
||||
} else if (image_min_size * 5 / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 5;
|
||||
jpeg_config_input.scale_denom = 8;
|
||||
} else if (image_min_size * 6 / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 6;
|
||||
jpeg_config_input.scale_denom = 8;
|
||||
} else if (image_min_size * 7 / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 7;
|
||||
jpeg_config_input.scale_denom = 8;
|
||||
}
|
||||
|
||||
DEBUG("start decompress");
|
||||
if(jpeg_start_decompress(&jpeg_config_input) == FALSE) {
|
||||
jpeg_destroy_decompress(&jpeg_config_input);
|
||||
return 1;
|
||||
}
|
||||
DEBUG("processing image");
|
||||
jpeg_row_stride = jpeg_config_input.output_width * jpeg_config_input.output_components;
|
||||
jpeg_config_output.image_width = jpeg_config_input.output_width;
|
||||
jpeg_config_output.image_height = jpeg_config_input.output_height;
|
||||
jpeg_config_output.input_components = jpeg_config_input.num_components;
|
||||
jpeg_config_output.in_color_space = JCS_RGB;
|
||||
jpeg_set_defaults(&jpeg_config_output);
|
||||
jpeg_set_quality(&jpeg_config_output, JPEG_QUALITY, TRUE);
|
||||
jpeg_start_compress(&jpeg_config_output, TRUE);
|
||||
|
||||
buffer = (*jpeg_config_input.mem->alloc_sarray) ((j_common_ptr) &jpeg_config_input, JPOOL_IMAGE, jpeg_row_stride, 1);
|
||||
while (jpeg_config_input.output_scanline < jpeg_config_input.output_height) {
|
||||
// TODO: scanlines should return 1
|
||||
jpeg_read_scanlines(&jpeg_config_input, buffer, 1);
|
||||
jpeg_write_scanlines(&jpeg_config_output, buffer, 1);
|
||||
}
|
||||
DEBUG("end decompress");
|
||||
jpeg_finish_decompress(&jpeg_config_input);
|
||||
jpeg_destroy_decompress(&jpeg_config_input);
|
||||
DEBUG("finish decompress");
|
||||
jpeg_finish_compress(&jpeg_config_output);
|
||||
DEBUG("final");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
static int MyWriter(const uint8_t* data, size_t data_size,
|
||||
const WebPPicture* const pic) {
|
||||
FILE* const out = (FILE*)pic->custom_ptr;
|
||||
return data_size ? (fwrite(data, data_size, 1, out) == 1) : 1;
|
||||
}
|
||||
|
||||
int jpeg_to_webp(FILE* input, FILE* output) {
|
||||
#ifdef HAS_DEBUG
|
||||
clock_t t;
|
||||
t = clock();
|
||||
#endif
|
||||
|
||||
struct jpeg_decompress_struct jpeg_config_input;
|
||||
struct jpeg_error_mgr jerr;
|
||||
u_int8_t* volatile rgb = NULL;
|
||||
JSAMPROW buffer[1];
|
||||
int jpeg_row_stride;
|
||||
|
||||
jpeg_config_input.err = jpeg_std_error(&jerr);
|
||||
jpeg_config_input.dct_method = JDCT_IFAST;
|
||||
jpeg_config_input.do_fancy_upsampling = FALSE;
|
||||
jpeg_config_input.two_pass_quantize = FALSE;
|
||||
jpeg_config_input.dither_mode = JDITHER_ORDERED;
|
||||
|
||||
jpeg_create_decompress(&jpeg_config_input);
|
||||
jpeg_stdio_src(&jpeg_config_input, input);
|
||||
|
||||
DEBUG("after constructor decompress");
|
||||
if(jpeg_read_header(&jpeg_config_input, TRUE) != JPEG_HEADER_OK) {
|
||||
jpeg_destroy_decompress(&jpeg_config_input);
|
||||
return 1;
|
||||
}
|
||||
DEBUG("after header read");
|
||||
jpeg_config_input.dct_method = JDCT_IFAST;
|
||||
jpeg_config_input.do_fancy_upsampling = FALSE;
|
||||
jpeg_config_input.two_pass_quantize = FALSE;
|
||||
jpeg_config_input.dither_mode = JDITHER_ORDERED;
|
||||
jpeg_calc_output_dimensions(&jpeg_config_input);
|
||||
DEBUG("start decompress");
|
||||
if(jpeg_start_decompress(&jpeg_config_input) == FALSE) {
|
||||
jpeg_destroy_decompress(&jpeg_config_input);
|
||||
return 1;
|
||||
}
|
||||
DEBUG("hot");
|
||||
jpeg_row_stride = jpeg_config_input.output_width * jpeg_config_input.output_components;
|
||||
|
||||
rgb = (u_int8_t*)malloc(jpeg_config_input.output_height * jpeg_row_stride);
|
||||
buffer[0] = (JSAMPLE*)rgb;
|
||||
while (jpeg_config_input.output_scanline < jpeg_config_input.output_height) {
|
||||
jpeg_read_scanlines(&jpeg_config_input, buffer, 1);
|
||||
buffer[0] += jpeg_row_stride;
|
||||
}
|
||||
DEBUG("end decompress");
|
||||
jpeg_finish_decompress(&jpeg_config_input);
|
||||
jpeg_destroy_decompress(&jpeg_config_input);
|
||||
|
||||
DEBUG("finish decompress");
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// ENCODE
|
||||
// resize: https://chromium.googlesource.com/webm/libwebp/+/0.2.0/examples/cwebp.c#1174
|
||||
WebPPicture picture;
|
||||
|
||||
if (!WebPPictureInit(&picture)) {
|
||||
DEBUG("ERR picture init");
|
||||
return 1;
|
||||
}
|
||||
|
||||
picture.width = jpeg_config_input.output_width;
|
||||
picture.height = jpeg_config_input.output_height;
|
||||
if(!WebPPictureAlloc(&picture)) {
|
||||
DEBUG("ALLOC ERR");
|
||||
return 1;
|
||||
}
|
||||
WebPPictureImportRGB(&picture, rgb, jpeg_row_stride);
|
||||
|
||||
WebPConfig webp_config_output;
|
||||
picture.writer = MyWriter;
|
||||
picture.custom_ptr = output;
|
||||
if (!WebPConfigInit(&webp_config_output)) {
|
||||
DEBUG("ERR config init");
|
||||
return 1;
|
||||
}
|
||||
webp_config_output.image_hint = WEBP_HINT_PHOTO;
|
||||
webp_config_output.method = 0;
|
||||
|
||||
if (!WebPValidateConfig(&webp_config_output)) {
|
||||
DEBUG("ERR WEB VALIDATION");
|
||||
}
|
||||
fprintf(stderr, "rescale start %F\n", ((double)clock() - t)/CLOCKS_PER_SEC * 1000);
|
||||
if (!WebPPictureRescale(&picture, jpeg_config_input.output_width / 4, jpeg_config_input.output_height / 4)) {
|
||||
DEBUG("ERR Rescale");
|
||||
}
|
||||
DEBUG("encoder start");
|
||||
WebPEncode(&webp_config_output, &picture);
|
||||
DEBUG("encoder done");
|
||||
WebPPictureFree(&picture);
|
||||
DEBUG("everything is free");
|
||||
}
|
||||
*/
|
||||
void jpeg_size(FILE* infile, int* height, int* width) {
|
||||
struct jpeg_decompress_struct cinfo;
|
||||
struct jpeg_error_mgr jerr;
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
|
||||
jpeg_create_decompress(&cinfo);
|
||||
jpeg_stdio_src(&cinfo, infile);
|
||||
jpeg_read_header(&cinfo, TRUE);
|
||||
jpeg_start_decompress(&cinfo);
|
||||
|
||||
*width = cinfo.image_width;
|
||||
*height = cinfo.image_height;
|
||||
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
}
|
||||
10
server/plugin/plg_image_thumbnail/src/image_jpeg.h
Normal file
10
server/plugin/plg_image_thumbnail/src/image_jpeg.h
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#include <stdio.h>
|
||||
#include "jpeglib.h"
|
||||
#include "utils.h"
|
||||
|
||||
void jpeg_size(FILE* infile, int* height, int* width);
|
||||
|
||||
int jpeg_to_jpeg(FILE* input, FILE* output);
|
||||
|
||||
int jpeg_to_webp(FILE* input, FILE* output);
|
||||
|
||||
|
|
@ -8,9 +8,7 @@ static int MyWriter(const uint8_t* data, size_t data_size, const WebPPicture* co
|
|||
return data_size ? (fwrite(data, data_size, 1, out) == 1) : 1;
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
FILE* input = stdin;
|
||||
FILE* output = stdout;
|
||||
int png_to_webp(FILE* input, FILE* output) {
|
||||
WebPPicture picture;
|
||||
|
||||
#ifdef HAS_DEBUG
|
||||
|
|
@ -20,12 +18,12 @@ int main(int argc, const char **argv) {
|
|||
png_image image;
|
||||
memset(&image, 0, sizeof image);
|
||||
image.version = PNG_IMAGE_VERSION;
|
||||
DEBUG("> reading png");
|
||||
DEBUG("reading png");
|
||||
if (!png_image_begin_read_from_stdio(&image, input)) {
|
||||
ERROR("png_image_begin_read_from_stdio");
|
||||
return 1;
|
||||
}
|
||||
DEBUG("> allocate");
|
||||
DEBUG("allocate");
|
||||
png_bytep buffer;
|
||||
image.format = PNG_FORMAT_RGBA;
|
||||
buffer = malloc(PNG_IMAGE_SIZE(image));
|
||||
|
|
@ -34,7 +32,7 @@ int main(int argc, const char **argv) {
|
|||
png_image_free(&image);
|
||||
return 1;
|
||||
}
|
||||
DEBUG("> start reading");
|
||||
DEBUG("start reading");
|
||||
if (!png_image_finish_read(&image, NULL, buffer, 0, NULL)) {
|
||||
ERROR("png_image_finish_read");
|
||||
png_image_free(&image);
|
||||
|
|
@ -44,7 +42,7 @@ int main(int argc, const char **argv) {
|
|||
|
||||
/////////////////////////////////////////////
|
||||
// encode to webp
|
||||
DEBUG("> start encoding");
|
||||
DEBUG("start encoding");
|
||||
if (!WebPPictureInit(&picture)) {
|
||||
ERROR("WebPPictureInit");
|
||||
png_image_free(&image);
|
||||
|
|
@ -59,7 +57,7 @@ int main(int argc, const char **argv) {
|
|||
free(buffer);
|
||||
return 1;
|
||||
}
|
||||
DEBUG("> start encoding import");
|
||||
DEBUG("start encoding import");
|
||||
WebPPictureImportRGBA(&picture, buffer, PNG_IMAGE_ROW_STRIDE(image));
|
||||
png_image_free(&image);
|
||||
free(buffer);
|
||||
|
|
@ -67,7 +65,7 @@ int main(int argc, const char **argv) {
|
|||
WebPConfig webp_config_output;
|
||||
picture.writer = MyWriter;
|
||||
picture.custom_ptr = output;
|
||||
DEBUG("> start encoding config init");
|
||||
DEBUG("start encoding config init");
|
||||
if (!WebPConfigInit(&webp_config_output)) {
|
||||
ERROR("ERR config init");
|
||||
WebPPictureFree(&picture);
|
||||
|
|
@ -80,7 +78,7 @@ int main(int argc, const char **argv) {
|
|||
WebPPictureFree(&picture);
|
||||
return 1;
|
||||
}
|
||||
DEBUG("> rescale start");
|
||||
DEBUG("rescale start");
|
||||
if (image.width > TARGET_SIZE && image.height > TARGET_SIZE) {
|
||||
float ratioHeight = (float) image.height / TARGET_SIZE;
|
||||
float ratioWidth = (float) image.width / TARGET_SIZE;
|
||||
|
|
@ -91,10 +89,54 @@ int main(int argc, const char **argv) {
|
|||
return 1;
|
||||
}
|
||||
}
|
||||
DEBUG("> encoder start");
|
||||
DEBUG("encoder start");
|
||||
WebPEncode(&webp_config_output, &picture);
|
||||
DEBUG("> encoder done");
|
||||
DEBUG("encoder done");
|
||||
WebPPictureFree(&picture);
|
||||
DEBUG("> cleaning up");
|
||||
DEBUG("cleaning up");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int png_to_png(FILE* input, FILE* output) {
|
||||
#ifdef HAS_DEBUG
|
||||
clock_t t;
|
||||
t = clock();
|
||||
#endif
|
||||
png_image image;
|
||||
memset(&image, 0, sizeof image);
|
||||
image.version = PNG_IMAGE_VERSION;
|
||||
DEBUG("> reading png");
|
||||
if (!png_image_begin_read_from_stdio(&image, input)) {
|
||||
DEBUG("png_image_begin_read_from_stdio");
|
||||
return 1;
|
||||
}
|
||||
DEBUG("> allocate");
|
||||
png_bytep buffer;
|
||||
image.format = PNG_FORMAT_RGBA;
|
||||
buffer = malloc(PNG_IMAGE_SIZE(image));
|
||||
if (buffer == NULL) {
|
||||
DEBUG("png_malloc");
|
||||
png_image_free(&image);
|
||||
return 1;
|
||||
}
|
||||
DEBUG("> start reading");
|
||||
if (!png_image_finish_read(&image, NULL, buffer, 0, NULL)) {
|
||||
DEBUG("png_image_finish_read");
|
||||
png_image_free(&image);
|
||||
free(buffer);
|
||||
return 1;
|
||||
}
|
||||
|
||||
DEBUG("> write");
|
||||
if (!png_image_write_to_stdio(&image, output, 0, buffer, 0, NULL)) {
|
||||
DEBUG("png_image_write_to_stdio");
|
||||
png_image_free(&image);
|
||||
free(buffer);
|
||||
return 1;
|
||||
}
|
||||
|
||||
DEBUG("> end");
|
||||
png_image_free(&image);
|
||||
free(buffer);
|
||||
return 0;
|
||||
}
|
||||
5
server/plugin/plg_image_thumbnail/src/image_png.h
Normal file
5
server/plugin/plg_image_thumbnail/src/image_png.h
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#include <stdio.h>
|
||||
|
||||
int png_to_webp(FILE* input, FILE* output);
|
||||
|
||||
int png_to_png(FILE* input, FILE* output);
|
||||
82
server/plugin/plg_image_thumbnail/src/image_raw.c
Normal file
82
server/plugin/plg_image_thumbnail/src/image_raw.c
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <libraw/libraw.h>
|
||||
#include "utils.h"
|
||||
#include "image_jpeg.h"
|
||||
|
||||
#define BUF_SIZE 1024 * 1024
|
||||
|
||||
int raw_to_jpeg(FILE* input, FILE* output) {
|
||||
#ifdef HAS_DEBUG
|
||||
clock_t t;
|
||||
t = clock();
|
||||
#endif
|
||||
|
||||
char fname_in[32] = "/tmp/filestash.XXXXXX";
|
||||
int _mkstemp_in = mkstemp(fname_in);
|
||||
if (_mkstemp_in == -1) {
|
||||
ERROR("mkstemp_in");
|
||||
return 1;
|
||||
}
|
||||
FILE* f_in = fdopen(_mkstemp_in, "w");
|
||||
if (f_in == NULL) {
|
||||
remove(fname_in);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char content[BUF_SIZE];
|
||||
int read;
|
||||
while ((read = fread(content, sizeof(char), BUF_SIZE, input))) {
|
||||
fwrite(content, read, sizeof(char), f_in);
|
||||
}
|
||||
|
||||
DEBUG("libraw init");
|
||||
libraw_data_t *raw = libraw_init(0);
|
||||
DEBUG("libraw open file");
|
||||
if (libraw_open_file(raw, fname_in) != 0) {
|
||||
ERROR("libraw_open_file");
|
||||
libraw_close(raw);
|
||||
fclose(f_in);
|
||||
remove(fname_in);
|
||||
return 1;
|
||||
}
|
||||
|
||||
raw->params.output_tiff = 1;
|
||||
DEBUG("libraw unpack thumb");
|
||||
char fname_out[32] = "/tmp/filestash.XXXXXX";
|
||||
int _mkstemp_out = mkstemp(fname_out);
|
||||
if (_mkstemp_out == -1) {
|
||||
ERROR("mkstemp_out");
|
||||
remove(fname_in);
|
||||
fclose(f_in);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (libraw_unpack_thumb(raw) == 0 && raw->thumbnail.tformat == LIBRAW_THUMBNAIL_JPEG) {
|
||||
DEBUG("has an embed thumbnail");
|
||||
if (libraw_dcraw_thumb_writer(raw, fname_out) == 0) {
|
||||
DEBUG("process thumbnail");
|
||||
libraw_close(raw);
|
||||
FILE* f_out = fdopen(_mkstemp_out, "r");
|
||||
int err = jpeg_to_jpeg(f_out, output);
|
||||
fclose(f_out);
|
||||
fclose(f_in);
|
||||
remove(fname_in);
|
||||
remove(fname_out);
|
||||
DEBUG("process complete");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
ERROR("not implemented - abort");
|
||||
// if (libraw_unpack(raw) != 0) DEBUG("HERE0");
|
||||
// if (libraw_dcraw_process(raw) != 0) DEBUG("HERE1");
|
||||
// if (libraw_dcraw_ppm_tiff_writer(raw, fname_out) != 0) DEBUG("HERE2");
|
||||
// if (libraw_dcraw_thumb_writer(raw, fname_out) != 0) DEBUG("HERE3");
|
||||
// DEBUG("HERE__");
|
||||
fclose(f_in);
|
||||
remove(fname_in);
|
||||
remove(fname_out);
|
||||
libraw_close(raw);
|
||||
return 1;
|
||||
}
|
||||
7
server/plugin/plg_image_thumbnail/src/image_raw.h
Normal file
7
server/plugin/plg_image_thumbnail/src/image_raw.h
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <libraw/libraw.h>
|
||||
#include "utils.h"
|
||||
#include "image_jpeg.h"
|
||||
|
||||
int raw_to_jpeg(FILE* input, FILE* output);
|
||||
27
server/plugin/plg_image_thumbnail/src/image_webp.c
Normal file
27
server/plugin/plg_image_thumbnail/src/image_webp.c
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#include <stdio.h>
|
||||
#include "webp/decode.h"
|
||||
|
||||
#define DEFAULT_SIZE 100
|
||||
#define STEP_SIZE 100
|
||||
|
||||
void webp_size(FILE* infile, int* height, int* width) {
|
||||
uint8_t *buffer[DEFAULT_SIZE];
|
||||
size_t buffer_sz=DEFAULT_SIZE;
|
||||
size_t i=0;
|
||||
while (!feof(infile)) {
|
||||
// buffer[i] = fgetc(infile);
|
||||
fread(buffer, buffer_sz+1, sizeof(char), infile);
|
||||
i++;
|
||||
if (i >= buffer_sz) {
|
||||
buffer_sz += STEP_SIZE;
|
||||
void *tmp = buffer;
|
||||
buffer = realloc(buffer, buffer_sz);
|
||||
if (buffer == NULL) {
|
||||
free(tmp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WebPGetInfo(buffer, buffer_sz, height, width);
|
||||
}
|
||||
3
server/plugin/plg_image_thumbnail/src/image_webp.h
Normal file
3
server/plugin/plg_image_thumbnail/src/image_webp.h
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#include <stdio.h>
|
||||
|
||||
void webp_size(FILE* infile, int* height, int* width);
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include "jpeglib.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define JPEG_QUALITY 50
|
||||
|
||||
#define min(a, b) (a > b ? b : a)
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
FILE* input = stdin;
|
||||
FILE* output = stdout;
|
||||
|
||||
#ifdef HAS_DEBUG
|
||||
clock_t t;
|
||||
t = clock();
|
||||
#endif
|
||||
|
||||
struct jpeg_decompress_struct jpeg_config_input;
|
||||
struct jpeg_compress_struct jpeg_config_output;
|
||||
struct jpeg_error_mgr jerr;
|
||||
int jpeg_row_stride;
|
||||
int image_min_size;
|
||||
JSAMPARRAY buffer;
|
||||
|
||||
jpeg_config_input.err = jpeg_std_error(&jerr);
|
||||
jpeg_config_output.err = jpeg_std_error(&jerr);
|
||||
jpeg_config_input.dct_method = JDCT_IFAST;
|
||||
jpeg_config_input.do_fancy_upsampling = FALSE;
|
||||
jpeg_config_input.two_pass_quantize = FALSE;
|
||||
jpeg_config_input.dither_mode = JDITHER_ORDERED;
|
||||
|
||||
jpeg_create_decompress(&jpeg_config_input);
|
||||
jpeg_create_compress(&jpeg_config_output);
|
||||
jpeg_stdio_src(&jpeg_config_input, input);
|
||||
jpeg_stdio_dest(&jpeg_config_output, output);
|
||||
DEBUG("> after constructor decompress");
|
||||
if(jpeg_read_header(&jpeg_config_input, TRUE) != JPEG_HEADER_OK) {
|
||||
jpeg_destroy_decompress(&jpeg_config_input);
|
||||
return 1;
|
||||
}
|
||||
DEBUG("> after header read");
|
||||
jpeg_config_input.dct_method = JDCT_IFAST;
|
||||
jpeg_config_input.do_fancy_upsampling = FALSE;
|
||||
jpeg_config_input.two_pass_quantize = FALSE;
|
||||
jpeg_config_input.dither_mode = JDITHER_ORDERED;
|
||||
jpeg_calc_output_dimensions(&jpeg_config_input);
|
||||
|
||||
image_min_size = min(jpeg_config_input.output_width, jpeg_config_input.output_height);
|
||||
jpeg_config_input.scale_num = 1;
|
||||
jpeg_config_input.scale_denom = 1;
|
||||
if (image_min_size / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 1;
|
||||
jpeg_config_input.scale_denom = 8;
|
||||
} else if (image_min_size * 2 / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 1;
|
||||
jpeg_config_input.scale_denom = 4;
|
||||
} else if (image_min_size * 3 / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 3;
|
||||
jpeg_config_input.scale_denom = 8;
|
||||
} else if (image_min_size * 4 / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 4;
|
||||
jpeg_config_input.scale_denom = 8;
|
||||
} else if (image_min_size * 5 / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 5;
|
||||
jpeg_config_input.scale_denom = 8;
|
||||
} else if (image_min_size * 6 / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 6;
|
||||
jpeg_config_input.scale_denom = 8;
|
||||
} else if (image_min_size * 7 / 8 >= TARGET_SIZE) {
|
||||
jpeg_config_input.scale_num = 7;
|
||||
jpeg_config_input.scale_denom = 8;
|
||||
}
|
||||
|
||||
DEBUG("> start decompress");
|
||||
if(jpeg_start_decompress(&jpeg_config_input) == FALSE) {
|
||||
jpeg_destroy_decompress(&jpeg_config_input);
|
||||
return 1;
|
||||
}
|
||||
DEBUG("> processing image");
|
||||
jpeg_row_stride = jpeg_config_input.output_width * jpeg_config_input.output_components;
|
||||
jpeg_config_output.image_width = jpeg_config_input.output_width;
|
||||
jpeg_config_output.image_height = jpeg_config_input.output_height;
|
||||
jpeg_config_output.input_components = jpeg_config_input.num_components;
|
||||
jpeg_config_output.in_color_space = JCS_RGB;
|
||||
jpeg_set_defaults(&jpeg_config_output);
|
||||
jpeg_set_quality(&jpeg_config_output, JPEG_QUALITY, TRUE);
|
||||
jpeg_start_compress(&jpeg_config_output, TRUE);
|
||||
|
||||
buffer = (*jpeg_config_input.mem->alloc_sarray) ((j_common_ptr) &jpeg_config_input, JPOOL_IMAGE, jpeg_row_stride, 1);
|
||||
while (jpeg_config_input.output_scanline < jpeg_config_input.output_height) {
|
||||
// TODO: scanlines should return 1
|
||||
jpeg_read_scanlines(&jpeg_config_input, buffer, 1);
|
||||
jpeg_write_scanlines(&jpeg_config_output, buffer, 1);
|
||||
}
|
||||
DEBUG("> end decompress");
|
||||
jpeg_finish_decompress(&jpeg_config_input);
|
||||
jpeg_destroy_decompress(&jpeg_config_input);
|
||||
DEBUG("> finish decompress");
|
||||
jpeg_finish_compress(&jpeg_config_output);
|
||||
DEBUG("> final");
|
||||
return 0;
|
||||
}
|
||||
7
server/plugin/plg_image_thumbnail/src/jpeg_to_jpeg.h
Normal file
7
server/plugin/plg_image_thumbnail/src/jpeg_to_jpeg.h
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#include <stdio.h>
|
||||
#include "jpeglib.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define JPEG_QUALITY 50
|
||||
|
||||
#define min(a, b) (a > b ? b : a)
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include "webp/encode.h"
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include "jpeglib.h"
|
||||
#include "utils.h"
|
||||
|
||||
static int MyWriter(const uint8_t* data, size_t data_size,
|
||||
const WebPPicture* const pic) {
|
||||
FILE* const out = (FILE*)pic->custom_ptr;
|
||||
return data_size ? (fwrite(data, data_size, 1, out) == 1) : 1;
|
||||
}
|
||||
|
||||
int main() {
|
||||
FILE* input = stdin;
|
||||
FILE* output = stdout;
|
||||
|
||||
#ifdef HAS_DEBUG
|
||||
clock_t t;
|
||||
t = clock();
|
||||
#endif
|
||||
|
||||
struct jpeg_decompress_struct jpeg_config_input;
|
||||
struct jpeg_error_mgr jerr;
|
||||
u_int8_t* volatile rgb = NULL;
|
||||
JSAMPROW buffer[1];
|
||||
int jpeg_row_stride;
|
||||
|
||||
jpeg_config_input.err = jpeg_std_error(&jerr);
|
||||
jpeg_config_input.dct_method = JDCT_IFAST;
|
||||
jpeg_config_input.do_fancy_upsampling = FALSE;
|
||||
jpeg_config_input.two_pass_quantize = FALSE;
|
||||
jpeg_config_input.dither_mode = JDITHER_ORDERED;
|
||||
|
||||
jpeg_create_decompress(&jpeg_config_input);
|
||||
jpeg_stdio_src(&jpeg_config_input, input);
|
||||
|
||||
DEBUG("- after constructor decompress");
|
||||
if(jpeg_read_header(&jpeg_config_input, TRUE) != JPEG_HEADER_OK) {
|
||||
jpeg_destroy_decompress(&jpeg_config_input);
|
||||
return 1;
|
||||
}
|
||||
DEBUG("- after header read");
|
||||
jpeg_config_input.dct_method = JDCT_IFAST;
|
||||
jpeg_config_input.do_fancy_upsampling = FALSE;
|
||||
jpeg_config_input.two_pass_quantize = FALSE;
|
||||
jpeg_config_input.dither_mode = JDITHER_ORDERED;
|
||||
jpeg_calc_output_dimensions(&jpeg_config_input);
|
||||
DEBUG("- start decompress");
|
||||
if(jpeg_start_decompress(&jpeg_config_input) == FALSE) {
|
||||
jpeg_destroy_decompress(&jpeg_config_input);
|
||||
return 1;
|
||||
}
|
||||
DEBUG("- hot");
|
||||
jpeg_row_stride = jpeg_config_input.output_width * jpeg_config_input.output_components;
|
||||
|
||||
rgb = (u_int8_t*)malloc(jpeg_config_input.output_height * jpeg_row_stride);
|
||||
buffer[0] = (JSAMPLE*)rgb;
|
||||
while (jpeg_config_input.output_scanline < jpeg_config_input.output_height) {
|
||||
jpeg_read_scanlines(&jpeg_config_input, buffer, 1);
|
||||
buffer[0] += jpeg_row_stride;
|
||||
}
|
||||
DEBUG("- end decompress");
|
||||
jpeg_finish_decompress(&jpeg_config_input);
|
||||
jpeg_destroy_decompress(&jpeg_config_input);
|
||||
|
||||
DEBUG("- finish decompress");
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// ENCODE
|
||||
// resize: https://chromium.googlesource.com/webm/libwebp/+/0.2.0/examples/cwebp.c#1174
|
||||
WebPPicture picture;
|
||||
|
||||
if (!WebPPictureInit(&picture)) {
|
||||
DEBUG("ERR picture init");
|
||||
return 1;
|
||||
}
|
||||
|
||||
picture.width = jpeg_config_input.output_width;
|
||||
picture.height = jpeg_config_input.output_height;
|
||||
if(!WebPPictureAlloc(&picture)) {
|
||||
DEBUG("ALLOC ERR");
|
||||
return 1;
|
||||
}
|
||||
WebPPictureImportRGB(&picture, rgb, jpeg_row_stride);
|
||||
|
||||
WebPConfig webp_config_output;
|
||||
picture.writer = MyWriter;
|
||||
picture.custom_ptr = output;
|
||||
if (!WebPConfigInit(&webp_config_output)) {
|
||||
DEBUG("ERR config init");
|
||||
return 1;
|
||||
}
|
||||
webp_config_output.image_hint = WEBP_HINT_PHOTO;
|
||||
webp_config_output.method = 0;
|
||||
|
||||
if (!WebPValidateConfig(&webp_config_output)) {
|
||||
DEBUG("ERR WEB VALIDATION");
|
||||
}
|
||||
fprintf(stderr, "- rescale start %F\n", ((double)clock() - t)/CLOCKS_PER_SEC * 1000);
|
||||
if (!WebPPictureRescale(&picture, jpeg_config_input.output_width / 4, jpeg_config_input.output_height / 4)) {
|
||||
DEBUG("ERR Rescale");
|
||||
}
|
||||
DEBUG("- encoder start");
|
||||
WebPEncode(&webp_config_output, &picture);
|
||||
DEBUG("- encoder done");
|
||||
WebPPictureFree(&picture);
|
||||
DEBUG("- everything is free");
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#include <stdio.h>
|
||||
#include "image_jpeg.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return jpeg_to_jpeg(stdin, stdout);
|
||||
}
|
||||
6
server/plugin/plg_image_thumbnail/src/main_png_to_webp.c
Normal file
6
server/plugin/plg_image_thumbnail/src/main_png_to_webp.c
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#include <stdio.h>
|
||||
#include "image_png.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return png_to_webp(stdin, stdout);
|
||||
}
|
||||
5
server/plugin/plg_image_thumbnail/src/main_raw_to_jpeg.c
Normal file
5
server/plugin/plg_image_thumbnail/src/main_raw_to_jpeg.c
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#include "image_raw.h"
|
||||
|
||||
int main(int args, const char **argv) {
|
||||
return raw_to_jpeg(stdin, stdout);
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <png.h>
|
||||
#include "utils.h"
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
FILE* input = stdin;
|
||||
FILE* output = stdout;
|
||||
|
||||
#ifdef HAS_DEBUG
|
||||
clock_t t;
|
||||
t = clock();
|
||||
#endif
|
||||
png_image image;
|
||||
memset(&image, 0, sizeof image);
|
||||
image.version = PNG_IMAGE_VERSION;
|
||||
DEBUG("> reading png");
|
||||
if (!png_image_begin_read_from_stdio(&image, input)) {
|
||||
DEBUG("png_image_begin_read_from_stdio");
|
||||
return 1;
|
||||
}
|
||||
DEBUG("> allocate");
|
||||
png_bytep buffer;
|
||||
image.format = PNG_FORMAT_RGBA;
|
||||
buffer = malloc(PNG_IMAGE_SIZE(image));
|
||||
if (buffer == NULL) {
|
||||
DEBUG("png_malloc");
|
||||
png_image_free(&image);
|
||||
return 1;
|
||||
}
|
||||
DEBUG("> start reading");
|
||||
if (!png_image_finish_read(&image, NULL, buffer, 0, NULL)) {
|
||||
DEBUG("png_image_finish_read");
|
||||
png_image_free(&image);
|
||||
free(buffer);
|
||||
return 1;
|
||||
}
|
||||
|
||||
DEBUG("> write");
|
||||
if (!png_image_write_to_stdio(&image, output, 0, buffer, 0, NULL)) {
|
||||
DEBUG("png_image_write_to_stdio");
|
||||
png_image_free(&image);
|
||||
free(buffer);
|
||||
return 1;
|
||||
}
|
||||
|
||||
DEBUG("> end");
|
||||
png_image_free(&image);
|
||||
free(buffer);
|
||||
return 0;
|
||||
}
|
||||
13
server/plugin/plg_image_thumbnail/src/png_to_webp.h
Normal file
13
server/plugin/plg_image_thumbnail/src/png_to_webp.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#include <string.h>
|
||||
#include <png.h>
|
||||
#include "webp/encode.h"
|
||||
#include "utils.h"
|
||||
|
||||
static int MyWriter(const uint8_t* data, size_t data_size, const WebPPicture* const pic) {
|
||||
FILE* const out = (FILE*)pic->custom_ptr;
|
||||
return data_size ? (fwrite(data, data_size, 1, out) == 1) : 1;
|
||||
}
|
||||
|
||||
int png_to_webp(FILE* input, FILE* output) {
|
||||
|
||||
}
|
||||
132
server/plugin/plg_image_thumbnail/src/test.c
Normal file
132
server/plugin/plg_image_thumbnail/src/test.c
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include "utils.h"
|
||||
#include "jpeg_to_jpeg.h"
|
||||
#include "png_to_webp.h"
|
||||
#include "jpeg.h"
|
||||
// #include "webp.h"
|
||||
|
||||
int strEndsWith(const char *s, const char *suff) {
|
||||
size_t slen = strlen(s);
|
||||
size_t sufflen = strlen(suff);
|
||||
return slen >= sufflen && !memcmp(s + slen - sufflen, suff, sufflen);
|
||||
}
|
||||
|
||||
void test_jpeg_to_jpeg(const char* basefolder) {
|
||||
struct dirent *de;
|
||||
DIR *dp;
|
||||
int n = 0;
|
||||
clock_t t = clock();
|
||||
|
||||
DEBUG("==================");
|
||||
DEBUG("TEST: jpeg_to_jpeg");
|
||||
dp = opendir(basefolder);
|
||||
assert(dp != NULL);
|
||||
remove("/tmp/out.dat");
|
||||
while ((de = readdir (dp)) != NULL) {
|
||||
if (strEndsWith(de->d_name, ".jpeg") == 0 && strEndsWith(de->d_name, ".jpg") == 0) {
|
||||
continue;
|
||||
}
|
||||
// STEP1: setup the test
|
||||
n += 1;
|
||||
char input_fname[2048] = "";
|
||||
strcpy(input_fname, basefolder);
|
||||
strcat(input_fname, de->d_name);
|
||||
fprintf(stderr, "= Processing[%s]:\n", input_fname);
|
||||
FILE* input = fopen(input_fname, "r");
|
||||
FILE* output = fopen("/tmp/out.dat", "w");
|
||||
|
||||
// STEP2: run the test
|
||||
int ret = jpeg_to_jpeg(input, output);
|
||||
fclose(input);
|
||||
fclose(output);
|
||||
|
||||
// STEP3: assertions
|
||||
int width = -1;
|
||||
int height = -1;
|
||||
output = fopen("/tmp/out.dat", "r");
|
||||
jpeg_size(output, &width, &height);
|
||||
fclose(output);
|
||||
if (width < 0 || width > 800) {
|
||||
fprintf(stderr, "%dx%d", width, height);
|
||||
assert("width outside range" == NULL);
|
||||
}
|
||||
if (height < 0 || height > 800) {
|
||||
fprintf(stderr, "height[%d]", height);
|
||||
assert("height outside range" == NULL);
|
||||
}
|
||||
assert(ret == 0);
|
||||
remove("/tmp/out.dat");
|
||||
}
|
||||
assert(n > 0);
|
||||
closedir(dp);
|
||||
}
|
||||
|
||||
void test_png_to_webp(const char* basefolder) {
|
||||
struct dirent *de;
|
||||
DIR *dp;
|
||||
int n = 0;
|
||||
clock_t t = clock();
|
||||
|
||||
DEBUG("==================");
|
||||
DEBUG("TEST: png_to_webp");
|
||||
dp = opendir(basefolder);
|
||||
assert(dp != NULL);
|
||||
remove("/tmp/out.dat");
|
||||
while ((de = readdir (dp)) != NULL) {
|
||||
if (strEndsWith(de->d_name, ".png") == 0) {
|
||||
continue;
|
||||
}
|
||||
// STEP1: setup the test
|
||||
n += 1;
|
||||
char input_fname[2048] = "";
|
||||
strcpy(input_fname, basefolder);
|
||||
strcat(input_fname, de->d_name);
|
||||
fprintf(stderr, "= Processing[%s]:\n", input_fname);
|
||||
FILE* input = fopen(input_fname, "r");
|
||||
FILE* output = fopen("/tmp/out.dat", "w");
|
||||
|
||||
// STEP2: run the test
|
||||
int ret = png_to_webp(input, output);
|
||||
fclose(input);
|
||||
fclose(output);
|
||||
|
||||
// STEP3: assertions
|
||||
int width = -1;
|
||||
int height = -1;
|
||||
output = fopen("/tmp/out.dat", "r");
|
||||
// webp_size(output, &width, &height);
|
||||
fclose(output);
|
||||
if (width < 0 || width > 800) {
|
||||
fprintf(stderr, "%dx%d", width, height);
|
||||
assert("width outside range" == NULL);
|
||||
}
|
||||
if (height < 0 || height > 800) {
|
||||
fprintf(stderr, "height[%d]", height);
|
||||
assert("height outside range" == NULL);
|
||||
}
|
||||
assert(ret == 0);
|
||||
remove("/tmp/out.dat");
|
||||
}
|
||||
assert(n > 0);
|
||||
closedir(dp);
|
||||
}
|
||||
|
||||
void test_raw_to_jpeg(const char* basename) {
|
||||
clock_t t = clock();
|
||||
DEBUG("==================");
|
||||
DEBUG("(TODO)TEST: raw_to_jpeg");
|
||||
}
|
||||
|
||||
int main(int args, const char **argv) {
|
||||
if (args != 2) {
|
||||
ERROR("need path with pictures in argument");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// test_jpeg_to_jpeg(argv[1]);
|
||||
// test_png_to_webp(argv[1]);
|
||||
// test_raw_to_jpeg(argv[1]);
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
#define TARGET_SIZE 250
|
||||
#define TARGET_SIZE 200
|
||||
|
||||
#define HAS_DEBUG 1
|
||||
#if HAS_DEBUG == 1
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#define DEBUG(r) (fprintf(stderr, r ": %.2Fms\n", ((double)clock() - t)/CLOCKS_PER_SEC * 1000))
|
||||
#define DEBUG(r) (fprintf(stderr, "[DEBUG::('" r "')(%.2Fms)]", ((double)clock() - t)/CLOCKS_PER_SEC * 1000))
|
||||
#else
|
||||
#define DEBUG(r) ((void)0)
|
||||
#endif
|
||||
|
||||
#define ERROR(r) (fprintf(stderr, r))
|
||||
#define ERROR(r) (fprintf(stderr, "[ERROR:('" r "')]"))
|
||||
|
|
|
|||
Loading…
Reference in a new issue