diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..000dc0a7 --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..397a4d9f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/bimg +/bundle +bin \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..17d01351 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: go +go: + - 1.4 + - 1.3 + - 1.2 + - release + - tip \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..b28d546c --- /dev/null +++ b/LICENSE @@ -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. \ No newline at end of file diff --git a/debug.go b/debug.go new file mode 100644 index 00000000..c781668b --- /dev/null +++ b/debug.go @@ -0,0 +1,5 @@ +package bimg + +import . "github.com/tj/go-debug" + +var debug = Debug("bimg") diff --git a/fixtures/space.jpg b/fixtures/space.jpg new file mode 100644 index 00000000..f17d2f18 Binary files /dev/null and b/fixtures/space.jpg differ diff --git a/fixtures/test.jpg b/fixtures/test.jpg new file mode 100644 index 00000000..3e0e61d7 Binary files /dev/null and b/fixtures/test.jpg differ diff --git a/image.go b/image.go index 71c24419..2b658b3f 100644 --- a/image.go +++ b/image.go @@ -1,3 +1,12 @@ package bimg -type Image struct {} +/* +#cgo pkg-config: vips +#include "vips/vips.h" +*/ +import "C" + +type Image struct { + buf []byte + image *C.struct__VipsImage +} diff --git a/options.go b/options.go new file mode 100644 index 00000000..bd2002f4 --- /dev/null +++ b/options.go @@ -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 +} diff --git a/resize.go b/resize.go new file mode 100644 index 00000000..c4a9ef91 --- /dev/null +++ b/resize.go @@ -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 +} diff --git a/resize_test.go b/resize_test.go new file mode 100644 index 00000000..b944b001 --- /dev/null +++ b/resize_test.go @@ -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") + } +} diff --git a/type.go b/type.go new file mode 100644 index 00000000..cd261189 --- /dev/null +++ b/type.go @@ -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"} +} diff --git a/vips.go b/vips.go new file mode 100644 index 00000000..baf24dfe --- /dev/null +++ b/vips.go @@ -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) +} diff --git a/vips.h b/vips.h new file mode 100644 index 00000000..7c288727 --- /dev/null +++ b/vips.h @@ -0,0 +1,91 @@ +#include +#include +#include + +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); +}