Skip to content

Commit

Permalink
feat(#1): initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
h2non committed Mar 29, 2015
1 parent 8bcefc7 commit 63f4b01
Show file tree
Hide file tree
Showing 14 changed files with 465 additions and 1 deletion.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/bimg
/bundle
bin
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language: go
go:
- 1.4
- 1.3
- 1.2
- release
- tip
24 changes: 24 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 5 additions & 0 deletions debug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package bimg

import . "github.com/tj/go-debug"

var debug = Debug("bimg")
Binary file added fixtures/space.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added fixtures/test.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion image.go
Original file line number Diff line number Diff line change
@@ -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
}
29 changes: 29 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
@@ -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
}
136 changes: 136 additions & 0 deletions resize.go
Original file line number Diff line number Diff line change
@@ -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
}
38 changes: 38 additions & 0 deletions resize_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
10 changes: 10 additions & 0 deletions type.go
Original file line number Diff line number Diff line change
@@ -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"}
}
100 changes: 100 additions & 0 deletions vips.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 63f4b01

Please sign in to comment.