Skip to content

Instantly share code, notes, and snippets.

@mickael-kerjean
Created November 6, 2023 21:53
Show Gist options
  • Save mickael-kerjean/4e6aab46807fc30cf553bad4f67ba9d6 to your computer and use it in GitHub Desktop.
Save mickael-kerjean/4e6aab46807fc30cf553bad4f67ba9d6 to your computer and use it in GitHub Desktop.
#include <string.h>
#include <png.h>
#include <stdlib.h>
#include "webp/encode.h"
#include "utils.h"
static int filestash_png_writer(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(int inputDesc, int outputDesc, int targetSize) {
#ifdef HAS_DEBUG
clock_t t;
t = clock();
#endif
WebPPicture picture;
FILE* input = fdopen(inputDesc, "r");
FILE* output = fdopen(outputDesc, "w");
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)) {
ERROR("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) {
ERROR("png_malloc");
png_image_free(&image);
return 1;
}
DEBUG("start reading");
if (!png_image_finish_read(&image, NULL, buffer, 0, NULL)) {
ERROR("png_image_finish_read");
png_image_free(&image);
free(buffer);
return 1;
}
/////////////////////////////////////////////
// encode to webp
/*
DEBUG("start encoding");
if (!WebPPictureInit(&picture)) {
ERROR("WebPPictureInit");
png_image_free(&image);
free(buffer);
return 1;
}
picture.width = image.width;
picture.height = image.height;
picture.writer = filestash_png_writer;
picture.custom_ptr = output;
if(!WebPPictureAlloc(&picture)) {
ERROR("WebPPictureAlloc");
png_image_free(&image);
free(buffer);
return 1;
}
DEBUG("start encoding import");
if(!WebPPictureImportRGBX(&picture, buffer, PNG_IMAGE_ROW_STRIDE(image))) {
ERROR("WebPPictureImport");
png_image_free(&image);
free(buffer);
return 1;
}
// png_image_free(&image);
// free(buffer);
WebPConfig webp_config_output;
DEBUG("start encoding config init");
if (!WebPConfigInit(&webp_config_output)) {
ERROR("ERR config init");
WebPPictureFree(&picture);
return 1;
}
WebPConfigLosslessPreset(&webp_config_output, 6); // TODO: 0 is faster
webp_config_output.method = 0;
webp_config_output.quality = 30;
if (!WebPValidateConfig(&webp_config_output)) {
ERROR("ERR WEBP VALIDATION");
WebPPictureFree(&picture);
return 1;
}
/*
DEBUG("rescale start");
if (image.width > targetSize && image.height > targetSize) {
float ratioHeight = (float) image.height / targetSize;
float ratioWidth = (float) image.width / targetSize;
float ratio = ratioWidth > ratioHeight ? ratioHeight : ratioWidth;
if (!WebPPictureRescale(&picture, image.width / ratio, image.height / ratio)) {
DEBUG("ERR Rescale");
WebPPictureFree(&picture);
return 1;
}
}
*//*
DEBUG("encoder start");
if (!WebPEncode(&webp_config_output, &picture)) {
ERROR("ERR WEBP ENCODE");
WebPPictureFree(&picture);
return 1;
}
DEBUG("encoder done");
WebPPictureFree(&picture);
DEBUG("cleaning up");
*/
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;
}
return 0;
}
int png_to_png(int inputDesc, int outputDesc, int targetSize) {
#ifdef HAS_DEBUG
clock_t t;
t = clock();
#endif
WebPPicture picture;
FILE* input = fdopen(inputDesc, "r");
FILE* output = fdopen(outputDesc, "w");
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;
}
package plg_image_c
// #include "image_png.h"
// #cgo LDFLAGS: -l:libpng.a -l:libz.a -l:libwebp.a -lpthread -lm
import "C"
func png(input uintptr, output uintptr, size int) {
C.png_to_png(C.int(input), C.int(output), 200)
return
}
#include <stdio.h>
#include <stdlib.h>
int png_to_png(int input, int output, int targetSize);
int png_to_webp(int input, int output, int targetSize);
package plg_image_c
import (
. "github.com/mickael-kerjean/filestash/server/common"
"io"
"net/http"
"os"
)
func init() {
Hooks.Register.Thumbnailer("image/jpeg", thumbnailer{
runner(jpeg),
"image/jpeg",
})
Hooks.Register.Thumbnailer("image/png", thumbnailer{
runner(png),
"image/png",
})
}
type thumbnailer struct {
fn func(input io.ReadCloser) (io.ReadCloser, error)
mime string
}
func (this thumbnailer) Generate(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
thumb, err := this.fn(reader)
if err == nil && this.mime != "" {
(*res).Header().Set("Content-Type", this.mime)
}
return thumb, err
}
/*
* uuuh, what is this stuff you might rightly wonder? Trying to send a go stream to C isn't obvious,
* but if you try to stream from C back to go in the same time, this is what you endup with.
* To my knowledge using file descriptor is the best way we can do that if we don't make the assumption
* that everything fits in memory.
*/
func runner(fn func(uintptr, uintptr, int)) func(io.ReadCloser) (io.ReadCloser, error) {
return func(inputGo io.ReadCloser) (io.ReadCloser, error) {
inputC, tmpw, err := os.Pipe()
if err != nil {
return nil, err
}
outputGo, outputC, err := os.Pipe()
if err != nil {
tmpw.Close()
return nil, err
}
go func() {
if _, err = io.Copy(tmpw, inputGo); err != nil {
Log.Error("plg_image_c::copy %s", err.Error())
}
}()
go func() {
fn(inputC.Fd(), outputC.Fd(), 200) // <-- all this code so we can do that
tmpw.Close()
outputC.Close()
inputC.Close()
}()
return outputGo, nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment