Example #1
0
File: vips.go Project: greut/bimg
func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) {
	defer C.g_object_unref(C.gpointer(image))

	tmpImage, err := vipsPreSave(image, &o)
	if err != nil {
		return nil, err
	}

	// When an image has an unsupported color space, vipsPreSave
	// returns the pointer of the image passed to it unmodified.
	// When this occurs, we must take care to not dereference the
	// original image a second time; we may otherwise erroneously
	// free the object twice.
	if tmpImage != image {
		defer C.g_object_unref(C.gpointer(tmpImage))
	}

	length := C.size_t(0)
	saveErr := C.int(0)
	interlace := C.int(boolToInt(o.Interlace))
	quality := C.int(o.Quality)

	var ptr unsafe.Pointer
	switch o.Type {
	case WEBP:
		saveErr = C.vips_webpsave_bridge(tmpImage, &ptr, &length, 1, quality)
		break
	case PNG:
		saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, 1, C.int(o.Compression), quality, interlace)
		break
	case GIF:
		return nil, errors.New("VIPS cannot save to GIF")
	case PDF:
		return nil, errors.New("VIPS cannot save to PDF")
	case SVG:
		return nil, errors.New("VIPS cannot save to SVG")
	default:
		saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, 1, quality, interlace)
		break
	}

	if int(saveErr) != 0 {
		return nil, catchVipsError()
	}

	buf := C.GoBytes(ptr, C.int(length))

	// Clean up
	C.g_free(C.gpointer(ptr))
	C.vips_error_clear()

	return buf, nil
}
Example #2
0
File: vips.go Project: greut/bimg
func getImageBuffer(image *C.VipsImage) ([]byte, error) {
	var ptr unsafe.Pointer

	length := C.size_t(0)
	interlace := C.int(0)
	quality := C.int(100)

	err := C.int(0)
	err = C.vips_jpegsave_bridge(image, &ptr, &length, 1, quality, interlace)
	if int(err) != 0 {
		return nil, catchVipsError()
	}

	defer C.g_free(C.gpointer(ptr))
	defer C.vips_error_clear()

	return C.GoBytes(ptr, C.int(length)), nil
}
Example #3
0
// vipsError converts from vips to Go errors.
func vipsError(e C.int) error {
	if e == 0 {
		return nil
	}

	// The VIPS error buffer is global, and checking and clearing it are
	// not atomic. If errors are infrequent, this will probably return
	// our error. It may also return nothing or unrelated errors.
	// TODO: Consider vips_error_freeze() and skipping this.
	s := C.GoString(C.vips_error_buffer())
	C.vips_error_clear()

	if s != "" {
		return errors.New(s)
	}

	// At least return something generic.
	return ErrImageOp
}
Example #4
0
File: vips.go Project: slav123/bimg
func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) {
	length := C.size_t(0)
	err := C.int(0)
	interlace := C.int(boolToInt(o.Interlace))

	// Remove ICC profile metadata
	if o.NoProfile {
		C.remove_profile(image)
	}

	// Force RGB color space
	var outImage *C.struct__VipsImage
	C.vips_colourspace_bridge(image, &outImage)

	defer C.g_object_unref(C.gpointer(image))
	defer C.g_object_unref(C.gpointer(outImage))

	var ptr unsafe.Pointer
	switch o.Type {
	case PNG:
		err = C.vips_pngsave_bridge(outImage, &ptr, &length, 1, C.int(o.Compression), C.int(o.Quality), interlace)
		break
	case WEBP:
		err = C.vips_webpsave_bridge(outImage, &ptr, &length, 1, C.int(o.Quality))
		break
	default:
		err = C.vips_jpegsave_bridge(outImage, &ptr, &length, 1, C.int(o.Quality), interlace)
		break
	}

	if int(err) != 0 {
		return nil, catchVipsError()
	}

	buf := C.GoBytes(ptr, C.int(length))

	// Clean up
	C.g_free(C.gpointer(ptr))
	C.vips_error_clear()

	return buf, nil
}
Example #5
0
File: vips.go Project: godeep/bimg
func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) {
	defer C.g_object_unref(C.gpointer(image))

	tmpImage, err := vipsPreSave(image, &o)
	if err != nil {
		return nil, err
	}
	defer C.g_object_unref(C.gpointer(tmpImage))

	length := C.size_t(0)
	saveErr := C.int(0)
	interlace := C.int(boolToInt(o.Interlace))
	quality := C.int(o.Quality)

	var ptr unsafe.Pointer
	switch o.Type {
	case WEBP:
		saveErr = C.vips_webpsave_bridge(tmpImage, &ptr, &length, 1, quality)
		break
	case PNG:
		saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, 1, C.int(o.Compression), quality, interlace)
		break
	default:
		saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, 1, quality, interlace)
		break
	}

	if int(saveErr) != 0 {
		return nil, catchVipsError()
	}

	buf := C.GoBytes(ptr, C.int(length))

	// Clean up
	C.g_free(C.gpointer(ptr))
	C.vips_error_clear()

	return buf, nil
}
Example #6
0
func resizeError() error {
	s := C.GoString(C.vips_error_buffer())
	C.vips_error_clear()
	return errors.New(s)
}
Example #7
0
func Resize(buf []byte, o Options) ([]byte, error) {
	debug("%#+v", o)

	// detect (if possible) the file type
	typ := UNKNOWN
	switch {
	case bytes.Equal(buf[:2], MARKER_JPEG):
		typ = JPEG
	case bytes.Equal(buf[:2], MARKER_PNG):
		typ = PNG
	default:
		return nil, errors.New("unknown image format")
	}

	// create an image instance
	var image, tmpImage *C.struct__VipsImage

	// feed it
	switch typ {
	case JPEG:
		C.vips_jpegload_buffer_seq(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &image)
	case PNG:
		C.vips_pngload_buffer_seq(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &image)
	}

	// cleanup
	defer func() {
		C.vips_thread_shutdown()
		C.vips_error_clear()
	}()

	// defaults
	if o.Quality == 0 {
		o.Quality = 100
	}

	// get WxH
	inWidth := int(image.Xsize)
	inHeight := int(image.Ysize)

	// prepare for factor
	factor := 0.0

	// image calculations
	switch {
	// Fixed width and height
	case o.Width > 0 && o.Height > 0:
		xf := float64(inWidth) / float64(o.Width)
		yf := float64(inHeight) / float64(o.Height)
		if o.Crop {
			factor = math.Min(xf, yf)
		} else {
			factor = math.Max(xf, yf)
		}
	// Fixed width, auto height
	case o.Width > 0:
		factor = float64(inWidth) / float64(o.Width)
		o.Height = int(math.Floor(float64(inHeight) / factor))
	// Fixed height, auto width
	case o.Height > 0:
		factor = float64(inHeight) / float64(o.Height)
		o.Width = int(math.Floor(float64(inWidth) / factor))
	// Identity transform
	default:
		factor = 1
		o.Width = inWidth
		o.Height = inHeight
	}

	debug("transform from %dx%d to %dx%d", inWidth, inHeight, o.Width, o.Height)

	// shrink
	shrink := int(math.Floor(factor))
	if shrink < 1 {
		shrink = 1
	}

	// residual
	residual := float64(shrink) / factor

	// Do not enlarge the output if the input width *or* height are already less than the required dimensions
	if !o.Enlarge {
		if inWidth < o.Width && inHeight < o.Height {
			factor = 1
			shrink = 1
			residual = 0
			o.Width = inWidth
			o.Height = inHeight
		}
	}

	debug("factor: %v, shrink: %v, residual: %v", factor, shrink, residual)

	// Try to use libjpeg shrink-on-load
	shrinkOnLoad := 1
	if typ == JPEG && shrink >= 2 {
		switch {
		case shrink >= 8:
			factor = factor / 8
			shrinkOnLoad = 8
		case shrink >= 4:
			factor = factor / 4
			shrinkOnLoad = 4
		case shrink >= 2:
			factor = factor / 2
			shrinkOnLoad = 2
		}
	}

	if shrinkOnLoad > 1 {
		debug("shrink on load %d", shrinkOnLoad)
		// Recalculate integral shrink and double residual
		factor = math.Max(factor, 1.0)
		shrink = int(math.Floor(factor))
		residual = float64(shrink) / factor
		// Reload input using shrink-on-load
		err := C.vips_jpegload_buffer_shrink(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &tmpImage, C.int(shrinkOnLoad))
		C.g_object_unref(C.gpointer(image))
		image = tmpImage
		if err != 0 {
			return nil, resizeError()
		}
	}

	if shrink > 1 {
		debug("shrink %d", shrink)
		// Use vips_shrink with the integral reduction
		err := C.vips_shrink_0(image, &tmpImage, C.double(float64(shrink)), C.double(float64(shrink)))
		C.g_object_unref(C.gpointer(image))
		image = tmpImage
		if err != 0 {
			return nil, resizeError()
		}

		// Recalculate residual float based on dimensions of required vs shrunk images
		shrunkWidth := int(image.Xsize)
		shrunkHeight := int(image.Ysize)

		residualx := float64(o.Width) / float64(shrunkWidth)
		residualy := float64(o.Height) / float64(shrunkHeight)
		if o.Crop {
			residual = math.Max(residualx, residualy)
		} else {
			residual = math.Min(residualx, residualy)
		}
	}

	// Use vips_affine with the remaining float part
	debug("residual: %v", residual)
	if residual != 0 {
		debug("residual %.2f", residual)
		// Create interpolator - "bilinear" (default), "bicubic" or "nohalo"
		is := C.CString(o.Interpolator.String())
		interpolator := C.vips_interpolate_new(is)

		// Perform affine transformation
		err := C.vips_affine_interpolator(image, &tmpImage, C.double(residual), 0, 0, C.double(residual), interpolator)
		C.g_object_unref(C.gpointer(image))

		image = tmpImage

		C.free(unsafe.Pointer(is))
		C.g_object_unref(C.gpointer(interpolator))

		if err != 0 {
			return nil, resizeError()
		}
	}

	// Crop/embed
	affinedWidth := int(image.Xsize)
	affinedHeight := int(image.Ysize)

	if affinedWidth != o.Width || affinedHeight != o.Height {
		if o.Crop {
			// Crop
			debug("cropping")
			left, top := sharpCalcCrop(affinedWidth, affinedHeight, o.Width, o.Height, o.Gravity)
			o.Width = int(math.Min(float64(affinedWidth), float64(o.Width)))
			o.Height = int(math.Min(float64(affinedHeight), float64(o.Height)))
			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
			if err != 0 {
				return nil, resizeError()
			}
		} else if o.Embed {
			debug("embedding with extend %d", o.Extend)
			left := (o.Width - affinedWidth) / 2
			top := (o.Height - affinedHeight) / 2
			err := C.vips_embed_extend(image, &tmpImage, C.int(left), C.int(top), C.int(o.Width), C.int(o.Height), C.int(o.Extend))
			C.g_object_unref(C.gpointer(image))
			image = tmpImage
			if err != 0 {
				return nil, resizeError()
			}
		}
	} else {
		debug("canvased same as affined")
	}

	// Always convert to sRGB colour space
	C.vips_colourspace_0(image, &tmpImage, C.VIPS_INTERPRETATION_sRGB)
	C.g_object_unref(C.gpointer(image))
	image = tmpImage

	// Finally save
	length := C.size_t(0)
	var ptr unsafe.Pointer
	C.vips_jpegsave_custom(image, &ptr, &length, 1, C.int(o.Quality), 0)
	C.g_object_unref(C.gpointer(image))

	// get back the buffer
	buf = C.GoBytes(ptr, C.int(length))
	C.g_free(C.gpointer(ptr))

	return buf, nil
}
Example #8
0
File: vips.go Project: godeep/bimg
func catchVipsError() error {
	s := C.GoString(C.vips_error_buffer())
	C.vips_error_clear()
	C.vips_thread_shutdown()
	return errors.New(s)
}
Example #9
0
func AutoRotate(file string, o Options) ([]byte, error) {
	debug("%#+v", o)

	// detect (if possible) the file type
	/*typ := UNKNOWN
	   	switch {
	   	case bytes.Equal(buf[:2], MARKER_JPEG):
	   		typ = JPEG
	   	case bytes.Equal(buf[:2], MARKER_PNG):
	   		typ = PNG
	   	default:
	   		return nil, errors.New("unknown image format")
	   	}

	   	// create an image instance
	   	var image, tmpImage *C.struct__VipsImage

		// feed it
	   	switch typ {
	   	case JPEG:
	   		C.vips_jpegload_buffer_seq(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &image)
	   	case PNG:
	   		C.vips_pngload_buffer_seq(unsafe.Pointer(&buf[0]), C.size_t(len(buf)), &image)
	   	}*/
	// create an image instance
	var image, tmpImage *C.struct__VipsImage

	image = C.vips_load_from_file(C.CString(file))

	// cleanup
	defer func() {
		C.vips_thread_shutdown()
		C.vips_error_clear()
	}()

	if image == nil {
		return nil, catchVipsError()
	}

	rotate, flip := calculateRotationAndFlip(image, 0)

	if rotate == 0 && !flip {
		return nil, nil //Remain unchanged
	}

	ret := C.vips_autorotate(image, &tmpImage)

	if ret != 0 {
		return nil, catchVipsError()
	}

	if o.Quality == 0 {
		o.Quality = 100
	}

	length := C.size_t(0)
	var ptr unsafe.Pointer

	switch o.Savetype {
	case WEBP:
		C.vips_webpsave_custom(tmpImage, &ptr, &length, C.int(o.Quality))
	case PNG:
		C.vips_pngsave_custom(tmpImage, &ptr, &length, 1, C.int(o.Quality), 0)
	default:
		C.vips_jpegsave_custom(tmpImage, &ptr, &length, 1, C.int(o.Quality), 0)
	}

	C.g_object_unref(C.gpointer(tmpImage))

	// get back the buffer
	var buf []byte
	buf = C.GoBytes(ptr, C.int(length))
	C.g_free(C.gpointer(ptr))
	return buf, nil
}