mirror of https://github.com/talgo-cloud/bimg.git
feat(#1): initial implementation
parent
8bcefc7736
commit
63f4b01c8d
@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tabs
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
/bimg
|
||||||
|
/bundle
|
||||||
|
bin
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.4
|
||||||
|
- 1.3
|
||||||
|
- 1.2
|
||||||
|
- release
|
||||||
|
- tip
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) Tomas Aparicio and contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package bimg
|
||||||
|
|
||||||
|
import . "github.com/tj/go-debug"
|
||||||
|
|
||||||
|
var debug = Debug("bimg")
|
||||||
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 96 KiB |
@ -1,3 +1,12 @@
|
|||||||
package bimg
|
package bimg
|
||||||
|
|
||||||
type Image struct {}
|
/*
|
||||||
|
#cgo pkg-config: vips
|
||||||
|
#include "vips/vips.h"
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
type Image struct {
|
||||||
|
buf []byte
|
||||||
|
image *C.struct__VipsImage
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
package bimg
|
||||||
|
|
||||||
|
const QUALITY = 80
|
||||||
|
|
||||||
|
type Gravity int
|
||||||
|
|
||||||
|
type Interpolator int
|
||||||
|
|
||||||
|
var interpolations = map[Interpolator]string{
|
||||||
|
BICUBIC: "bicubic",
|
||||||
|
BILINEAR: "bilinear",
|
||||||
|
NOHALO: "nohalo",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Interpolator) String() string {
|
||||||
|
return interpolations[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Height int
|
||||||
|
Width int
|
||||||
|
Crop bool
|
||||||
|
Enlarge bool
|
||||||
|
Extend int
|
||||||
|
Embed bool
|
||||||
|
Interpolator Interpolator
|
||||||
|
Gravity Gravity
|
||||||
|
Quality int
|
||||||
|
}
|
||||||
@ -0,0 +1,136 @@
|
|||||||
|
package bimg
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo pkg-config: vips
|
||||||
|
#include "vips/vips.h"
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BICUBIC Interpolator = iota
|
||||||
|
BILINEAR
|
||||||
|
NOHALO
|
||||||
|
)
|
||||||
|
|
||||||
|
func Resize(buf []byte, o Options) ([]byte, error) {
|
||||||
|
// detect (if possible) the file type
|
||||||
|
|
||||||
|
defer C.vips_thread_shutdown()
|
||||||
|
|
||||||
|
image, err := vipsRead(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//var tmpImage *C.struct__VipsImage
|
||||||
|
/*
|
||||||
|
// feed it
|
||||||
|
imageLength := C.size_t(len(buf))
|
||||||
|
imageBuf := unsafe.Pointer(&buf[0])
|
||||||
|
debug("buffer: %s", buf[0])
|
||||||
|
|
||||||
|
C.vips_jpegload_buffer_seq(imageBuf, imageLength, &image)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// defaults
|
||||||
|
if o.Quality == 0 {
|
||||||
|
o.Quality = QUALITY
|
||||||
|
}
|
||||||
|
|
||||||
|
// get WxH
|
||||||
|
inWidth := int(image.Xsize)
|
||||||
|
inHeight := int(image.Ysize)
|
||||||
|
|
||||||
|
// crop
|
||||||
|
|
||||||
|
if o.Crop {
|
||||||
|
left, top := calculateCrop(inWidth, inHeight, o.Width, o.Height, o.Gravity)
|
||||||
|
o.Width = int(math.Min(float64(inWidth), float64(o.Width)))
|
||||||
|
o.Height = int(math.Min(float64(inHeight), float64(o.Height)))
|
||||||
|
image, err = vipsExtract(image, left, top, o.Width, o.Height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//err := C.vips_extract_area_0(image, &tmpImage, C.int(left), C.int(top), C.int(o.Width), C.int(o.Height))
|
||||||
|
//C.g_object_unref(C.gpointer(image))
|
||||||
|
//image = tmpImage
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotate
|
||||||
|
r := Rotation{180}
|
||||||
|
image, err = Rotate(image, r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally save
|
||||||
|
//var ptr unsafe.Pointer
|
||||||
|
//length := C.size_t(0)
|
||||||
|
|
||||||
|
//C.vips_jpegsave_custom(image, &ptr, &length, 1, C.int(o.Quality), 0)
|
||||||
|
//C.g_object_unref(C.gpointer(image))
|
||||||
|
//C.g_object_unref(C.gpointer(newImage))
|
||||||
|
|
||||||
|
// get back the buffer
|
||||||
|
//buf = C.GoBytes(ptr, C.int(length))
|
||||||
|
// cleanup
|
||||||
|
//C.g_free(C.gpointer(ptr))
|
||||||
|
|
||||||
|
buf, err = vipsSave(image, vipsSaveOptions{Quality: o.Quality})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
C.vips_error_clear()
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rotation struct {
|
||||||
|
angle int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Rotation) calculate() int {
|
||||||
|
angle := a.angle
|
||||||
|
divisor := angle % 90
|
||||||
|
if divisor != 0 {
|
||||||
|
angle = a.angle - divisor
|
||||||
|
}
|
||||||
|
return angle
|
||||||
|
}
|
||||||
|
|
||||||
|
func Rotate(image *C.struct__VipsImage, r Rotation) (*C.struct__VipsImage, error) {
|
||||||
|
//vips := &Vips{}
|
||||||
|
return vipsRotate(image, r.calculate())
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
CENTRE Gravity = iota
|
||||||
|
NORTH
|
||||||
|
EAST
|
||||||
|
SOUTH
|
||||||
|
WEST
|
||||||
|
)
|
||||||
|
|
||||||
|
func calculateCrop(inWidth, inHeight, outWidth, outHeight int, gravity Gravity) (int, int) {
|
||||||
|
left, top := 0, 0
|
||||||
|
switch gravity {
|
||||||
|
case NORTH:
|
||||||
|
left = (inWidth - outWidth + 1) / 2
|
||||||
|
case EAST:
|
||||||
|
left = inWidth - outWidth
|
||||||
|
top = (inHeight - outHeight + 1) / 2
|
||||||
|
case SOUTH:
|
||||||
|
left = (inWidth - outWidth + 1) / 2
|
||||||
|
top = inHeight - outHeight
|
||||||
|
case WEST:
|
||||||
|
top = (inHeight - outHeight + 1) / 2
|
||||||
|
default:
|
||||||
|
left = (inWidth - outWidth + 1) / 2
|
||||||
|
top = (inHeight - outHeight + 1) / 2
|
||||||
|
}
|
||||||
|
return left, top
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package bimg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResize(t *testing.T) {
|
||||||
|
options := Options{Width: 800, Height: 600, Crop: false}
|
||||||
|
img, err := os.Open("fixtures/space.jpg")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer img.Close()
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(img)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newImg, err := Resize(buf, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Image %s", http.DetectContentType(newImg))
|
||||||
|
|
||||||
|
if http.DetectContentType(newImg) != "image/jpeg" {
|
||||||
|
t.Fatal("Image is not jpeg")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile("fixtures/test.jpg", newImg, 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Cannot save the image")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package bimg
|
||||||
|
|
||||||
|
type Type struct {
|
||||||
|
Name string
|
||||||
|
Mime string
|
||||||
|
}
|
||||||
|
|
||||||
|
func DetermineType(buf []byte) *Type {
|
||||||
|
return &Type{Name: "jpg", Mime: "image/jpg"}
|
||||||
|
}
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
package bimg
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo pkg-config: vips
|
||||||
|
#include "vips.h"
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
err := C.vips_init(C.CString("bimg"))
|
||||||
|
if err != 0 {
|
||||||
|
C.vips_shutdown()
|
||||||
|
panic("unable to start vips!")
|
||||||
|
}
|
||||||
|
|
||||||
|
C.vips_concurrency_set(1) // default
|
||||||
|
C.vips_cache_set_max_mem(100 * 1024 * 1024) // 100 MB
|
||||||
|
C.vips_cache_set_max(500) // 500 operations
|
||||||
|
}
|
||||||
|
|
||||||
|
type Vips struct {
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsRotate(image *C.struct__VipsImage, degrees int) (*C.struct__VipsImage, error) {
|
||||||
|
var out *C.struct__VipsImage
|
||||||
|
|
||||||
|
err := C.vips_rotate(image, &out, C.int(degrees))
|
||||||
|
C.g_object_unref(C.gpointer(image))
|
||||||
|
if err != 0 {
|
||||||
|
return nil, vipsError()
|
||||||
|
}
|
||||||
|
defer C.g_object_unref(C.gpointer(out))
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsRead(buf []byte) (*C.struct__VipsImage, error) {
|
||||||
|
var image *C.struct__VipsImage
|
||||||
|
|
||||||
|
// feed it
|
||||||
|
length := C.size_t(len(buf))
|
||||||
|
imageBuf := unsafe.Pointer(&buf[0])
|
||||||
|
|
||||||
|
err := C.vips_jpegload_buffer_seq(imageBuf, length, &image)
|
||||||
|
if err != 0 {
|
||||||
|
return nil, vipsError()
|
||||||
|
}
|
||||||
|
|
||||||
|
return image, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsExtract(image *C.struct__VipsImage, left int, top int, width int, height int) (*C.struct__VipsImage, error) {
|
||||||
|
var buf *C.struct__VipsImage
|
||||||
|
|
||||||
|
err := C.vips_extract_area_0(image, &buf, C.int(left), C.int(top), C.int(width), C.int(height))
|
||||||
|
C.g_object_unref(C.gpointer(image))
|
||||||
|
if err != 0 {
|
||||||
|
return nil, vipsError()
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type vipsSaveOptions struct {
|
||||||
|
Quality int
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) {
|
||||||
|
var ptr unsafe.Pointer
|
||||||
|
length := C.size_t(0)
|
||||||
|
|
||||||
|
err := C.vips_jpegsave_custom(image, &ptr, &length, 1, C.int(o.Quality), 0)
|
||||||
|
if err != 0 {
|
||||||
|
return nil, vipsError()
|
||||||
|
}
|
||||||
|
C.g_object_unref(C.gpointer(image))
|
||||||
|
|
||||||
|
buf := C.GoBytes(ptr, C.int(length))
|
||||||
|
// cleanup
|
||||||
|
C.g_free(C.gpointer(ptr))
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vipsError() error {
|
||||||
|
s := C.GoString(C.vips_error_buffer())
|
||||||
|
C.vips_error_clear()
|
||||||
|
C.vips_thread_shutdown()
|
||||||
|
return errors.New(s)
|
||||||
|
}
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <vips/vips.h>
|
||||||
|
#include <vips/vips7compat.h>
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_initialize()
|
||||||
|
{
|
||||||
|
return vips_init("bimg");
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_affine_interpolator(VipsImage *in, VipsImage **out, double a, double b, double c, double d, VipsInterpolate *interpolator)
|
||||||
|
{
|
||||||
|
return vips_affine(in, out, a, b, c, d, "interpolate", interpolator, NULL);
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_jpegload_buffer_seq(void *buf, size_t len, VipsImage **out)
|
||||||
|
{
|
||||||
|
return vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_jpegload_buffer_shrink(void *buf, size_t len, VipsImage **out, int shrink)
|
||||||
|
{
|
||||||
|
return vips_jpegload_buffer(buf, len, out, "shrink", shrink, NULL);
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_webpload_buffer_seq(void *buf, size_t len, VipsImage **out)
|
||||||
|
{
|
||||||
|
return vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_pngload_buffer_seq(void *buf, size_t len, VipsImage **out)
|
||||||
|
{
|
||||||
|
return vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_shrink_0(VipsImage *in, VipsImage **out, double xshrink, double yshrink)
|
||||||
|
{
|
||||||
|
return vips_shrink(in, out, xshrink, yshrink, NULL);
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_copy_0(VipsImage *in, VipsImage **out)
|
||||||
|
{
|
||||||
|
return vips_copy(in, out, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_rotate(VipsImage *in, VipsImage **buf, int angle)
|
||||||
|
{
|
||||||
|
int rotate = VIPS_ANGLE_D0;
|
||||||
|
|
||||||
|
if (angle == 90) {
|
||||||
|
rotate = VIPS_ANGLE_D90;
|
||||||
|
} else if (angle == 180) {
|
||||||
|
rotate = VIPS_ANGLE_D180;
|
||||||
|
} else if (angle == 270) {
|
||||||
|
rotate = VIPS_ANGLE_D270;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vips_rot(in, buf, rotate, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_embed_extend(VipsImage *in, VipsImage **out, int left, int top, int width, int height, int extend)
|
||||||
|
{
|
||||||
|
return vips_embed(in, out, left, top, width, height, "extend", extend, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_colourspace_0(VipsImage *in, VipsImage **out, VipsInterpretation space)
|
||||||
|
{
|
||||||
|
return vips_colourspace(in, out, space, NULL);
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_extract_area_0(VipsImage *in, VipsImage **out, int left, int top, int width, int height)
|
||||||
|
{
|
||||||
|
return vips_extract_area(in, out, left, top, width, height, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_jpegsave_custom(VipsImage *in, void **buf, size_t *len, int strip, int quality, int interlace)
|
||||||
|
{
|
||||||
|
return vips_jpegsave_buffer(in, buf, len, "strip", strip, "Q", quality, "optimize_coding", TRUE, "interlace", interlace, NULL);
|
||||||
|
}
|
||||||
Loading…
Reference in new issue