Git Product home page Git Product logo

gift's Introduction

GO IMAGE FILTERING TOOLKIT (GIFT)

GoDoc Build Status Coverage Status Go Report Card

Package gift provides a set of useful image processing filters.

Pure Go. No external dependencies outside of the Go standard library.

INSTALLATION / UPDATING

go get -u github.com/disintegration/gift

DOCUMENTATION

http://godoc.org/github.com/disintegration/gift

QUICK START

// 1. Create a new filter list and add some filters.
g := gift.New(
	gift.Resize(800, 0, gift.LanczosResampling),
	gift.UnsharpMask(1, 1, 0),
)

// 2. Create a new image of the corresponding size.
// dst is a new target image, src is the original image.
dst := image.NewRGBA(g.Bounds(src.Bounds()))

// 3. Use the Draw func to apply the filters to src and store the result in dst.
g.Draw(dst, src)

USAGE

To create a sequence of filters, the New function is used:

g := gift.New(
	gift.Grayscale(),
	gift.Contrast(10),
)

Filters also can be added using the Add method:

g.Add(GaussianBlur(2))

The Bounds method takes the bounds of the source image and returns appropriate bounds for the destination image to fit the result (for example, after using Resize or Rotate filters).

dst := image.NewRGBA(g.Bounds(src.Bounds()))

There are two methods available to apply these filters to an image:

  • Draw applies all the added filters to the src image and outputs the result to the dst image starting from the top-left corner (Min point).
g.Draw(dst, src)
  • DrawAt provides more control. It outputs the filtered src image to the dst image at the specified position using the specified image composition operator. This example is equivalent to the previous:
g.DrawAt(dst, src, dst.Bounds().Min, gift.CopyOperator)

Two image composition operators are supported by now:

  • CopyOperator - Replaces pixels of the dst image with pixels of the filtered src image. This mode is used by the Draw method.
  • OverOperator - Places the filtered src image on top of the dst image. This mode makes sence if the filtered src image has transparent areas.

Empty filter list can be used to create a copy of an image or to paste one image to another. For example:

// Create a new image with dimensions of the bgImage.
dstImage := image.NewRGBA(bgImage.Bounds())
// Copy the bgImage to the dstImage.
gift.New().Draw(dstImage, bgImage)
// Draw the fgImage over the dstImage at the (100, 100) position.
gift.New().DrawAt(dstImage, fgImage, image.Pt(100, 100), gift.OverOperator)

SUPPORTED FILTERS

  • Transformations

    • Crop(rect image.Rectangle)
    • CropToSize(width, height int, anchor Anchor)
    • FlipHorizontal()
    • FlipVertical()
    • Resize(width, height int, resampling Resampling)
    • ResizeToFill(width, height int, resampling Resampling, anchor Anchor)
    • ResizeToFit(width, height int, resampling Resampling)
    • Rotate(angle float32, backgroundColor color.Color, interpolation Interpolation)
    • Rotate180()
    • Rotate270()
    • Rotate90()
    • Transpose()
    • Transverse()
  • Adjustments & effects

    • Brightness(percentage float32)
    • ColorBalance(percentageRed, percentageGreen, percentageBlue float32)
    • ColorFunc(fn func(r0, g0, b0, a0 float32) (r, g, b, a float32))
    • Colorize(hue, saturation, percentage float32)
    • ColorspaceLinearToSRGB()
    • ColorspaceSRGBToLinear()
    • Contrast(percentage float32)
    • Convolution(kernel []float32, normalize, alpha, abs bool, delta float32)
    • Gamma(gamma float32)
    • GaussianBlur(sigma float32)
    • Grayscale()
    • Hue(shift float32)
    • Invert()
    • Maximum(ksize int, disk bool)
    • Mean(ksize int, disk bool)
    • Median(ksize int, disk bool)
    • Minimum(ksize int, disk bool)
    • Pixelate(size int)
    • Saturation(percentage float32)
    • Sepia(percentage float32)
    • Sigmoid(midpoint, factor float32)
    • Sobel()
    • Threshold(percentage float32)
    • UnsharpMask(sigma, amount, threshold float32)

FILTER EXAMPLES

The original image:

Resulting images after applying some of the filters:

name / result name / result name / result name / result
resize crop_to_size rotate_180 rotate_30
brightness_increase brightness_decrease contrast_increase contrast_decrease
saturation_increase saturation_decrease gamma_1.5 gamma_0.5
gaussian_blur unsharp_mask sigmoid pixelate
colorize grayscale sepia invert
mean median minimum maximum
hue_rotate color_balance color_func convolution_emboss

Here's the code that produces the above images:

package main

import (
	"image"
	"image/color"
	"image/png"
	"log"
	"os"

	"github.com/disintegration/gift"
)

func main() {
	src := loadImage("testdata/src.png")

	filters := map[string]gift.Filter{
		"resize":               gift.Resize(100, 0, gift.LanczosResampling),
		"crop_to_size":         gift.CropToSize(100, 100, gift.LeftAnchor),
		"rotate_180":           gift.Rotate180(),
		"rotate_30":            gift.Rotate(30, color.Transparent, gift.CubicInterpolation),
		"brightness_increase":  gift.Brightness(30),
		"brightness_decrease":  gift.Brightness(-30),
		"contrast_increase":    gift.Contrast(30),
		"contrast_decrease":    gift.Contrast(-30),
		"saturation_increase":  gift.Saturation(50),
		"saturation_decrease":  gift.Saturation(-50),
		"gamma_1.5":            gift.Gamma(1.5),
		"gamma_0.5":            gift.Gamma(0.5),
		"gaussian_blur":        gift.GaussianBlur(1),
		"unsharp_mask":         gift.UnsharpMask(1, 1, 0),
		"sigmoid":              gift.Sigmoid(0.5, 7),
		"pixelate":             gift.Pixelate(5),
		"colorize":             gift.Colorize(240, 50, 100),
		"grayscale":            gift.Grayscale(),
		"sepia":                gift.Sepia(100),
		"invert":               gift.Invert(),
		"mean":                 gift.Mean(5, true),
		"median":               gift.Median(5, true),
		"minimum":              gift.Minimum(5, true),
		"maximum":              gift.Maximum(5, true),
		"hue_rotate":           gift.Hue(45),
		"color_balance":        gift.ColorBalance(10, -10, -10),
		"color_func": gift.ColorFunc(
			func(r0, g0, b0, a0 float32) (r, g, b, a float32) {
				r = 1 - r0   // invert the red channel
				g = g0 + 0.1 // shift the green channel by 0.1
				b = 0        // set the blue channel to 0
				a = a0       // preserve the alpha channel
				return r, g, b, a
			},
		),
		"convolution_emboss": gift.Convolution(
			[]float32{
				-1, -1, 0,
				-1, 1, 1,
				0, 1, 1,
			},
			false, false, false, 0.0,
		),
	}

	for name, filter := range filters {
		g := gift.New(filter)
		dst := image.NewNRGBA(g.Bounds(src.Bounds()))
		g.Draw(dst, src)
		saveImage("testdata/dst_"+name+".png", dst)
	}
}

func loadImage(filename string) image.Image {
	f, err := os.Open(filename)
	if err != nil {
		log.Fatalf("os.Open failed: %v", err)
	}
	defer f.Close()
	img, _, err := image.Decode(f)
	if err != nil {
		log.Fatalf("image.Decode failed: %v", err)
	}
	return img
}

func saveImage(filename string, img image.Image) {
	f, err := os.Create(filename)
	if err != nil {
		log.Fatalf("os.Create failed: %v", err)
	}
	defer f.Close()
	err = png.Encode(f, img)
	if err != nil {
		log.Fatalf("png.Encode failed: %v", err)
	}
}

gift's People

Contributors

disintegration avatar dthadi3 avatar nixterrimus avatar qt-luigi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gift's Issues

Feature Request: replace Bound(src.Bounds) by Bound(src)

Hi,

I want to create a CropMargin filter. This filter will look in the img, and remove the white line all around the image.
For this I need the source image to look for the pixel I want to remove.

If the Bounds method receive the src instead the src.Bounds, that would make a lot for filter possible.

For now I have to calculate the bound before I can create the filter Crop and apply it.

May be I can return the srcBound, and in the Draw I can draw the return the src.sub image. That may work, but the g.Bounds will return the wrong value.

Do you think it could be a possible evolution?

Benchmarks would be nice

Hey there, nice project!

Having benchmarks to compare to other solutions (like bindings to ImageMagick et al) would be nice.

I'm idly looking for this sort of libraries and waiting to find one that performs well enough, having numbers would be great. It's also helpful when profiling.

If you'd like, I might be able to contribute some of them.

Posterize

Hello, is there a Posterize filter? If not, would you be interested in a contribution?

Feature request: Perceptual hash

It would be awesome to have this feature. Even simple average hash would be cool for the beginning.
Just need int64 that represents visual hash of the image.
Thank you!

DrawAt with OverOperator - White border around transparent background image

First off, this is a great library. I have been using gift with great results. Props to the author(s).

This issue is in regards to a white border rendered around images with a transparent background when using DrawAt with the OverOperator to write the image onto another image. My intent is to have transparent pngs render with no white border on top of other images.

Is this result the intended functionality of the OverOperator functionality? Is there any strategy I can take to get around this?

Example code below:

package main

import (
    "image"
    "image/color"
    "image/draw"
    "image/png"
    "os"

    "github.com/disintegration/gift"
)

func main() {
    f, err := os.Open("test.png")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    srcImg, _, err := image.Decode(f)
    if err != nil {
        panic(err)
    }

    finalImg := image.NewRGBA(image.Rect(0, 0, 300, 300))

    //draw background color (dk blue) on final image
    bgColor := color.RGBA{0, 0, 100, 255}
    draw.Draw(finalImg, finalImg.Bounds(), &image.Uniform{bgColor}, image.ZP, draw.Src)

    gift.New().DrawAt(finalImg, srcImg, image.Point{0, 0}, gift.OverOperator)

    fOut, err := os.Create("output.png")
    if err != nil {
        panic(err)
    }

    png.Encode(fOut, finalImg)
}

Source transparent background png:

test

Output image:

output

Extremely Slow.

My Diffusion-Reaction simulation attempt. 18 seconds for 900 filters ( 512x512 image). Also I experienced freezes while playing with UnsharpMask/GaussianBlur parameters.

	iteration := 300

	filter_set := []gift.Filter{
		gift.GaussianBlur(9),
		gift.UnsharpMask(9, 9, 0),
		gift.Threshold(50),
	}
	filters := []gift.Filter{}

	for i := 0; i < iteration; i++ {
		filters = append(filters, filter_set...)
	}

	g := gift.New(filters...)

input image

input

output image after 300 iteration.

output

Full code

package main

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"log"
	"os"
	"time"

	"github.com/disintegration/gift"
	"github.com/fogleman/gg"
)

func main() {
	start := time.Now()

	circleimage := image.NewRGBA(image.Rect(0, 0, 512, 512))
	draw.Draw(circleimage, circleimage.Bounds(), &image.Uniform{color.Black}, image.ZP, draw.Src)
	dc := gg.NewContextForImage(circleimage)
	dc.DrawCircle(128, 128, 32)
	dc.SetRGB(1, 1, 1)
	dc.Fill()
	input := dc.Image()
	// src := loadImage("input.png")
	iteration := 300

	filter_set := []gift.Filter{
		gift.GaussianBlur(9),
		gift.UnsharpMask(9, 9, 0),
		gift.Threshold(50),
	}
	filters := []gift.Filter{}

	for i := 0; i < iteration; i++ {
		filters = append(filters, filter_set...)
	}

	g := gift.New(filters...)

	dst := image.NewRGBA(g.Bounds(input.Bounds()))

	g.Draw(dst, input)
	saveImage("input.png", input)
	saveImage("output.png", dst)

	duration := time.Since(start)

	// Formatted string, such as "2h3m0.5s" or "4.503μs"
	fmt.Println(duration)

}

func loadImage(filename string) image.Image {
	f, err := os.Open(filename)
	if err != nil {
		log.Fatalf("os.Open failed: %v", err)
	}
	defer f.Close()
	img, _, err := image.Decode(f)
	if err != nil {
		log.Fatalf("image.Decode failed: %v", err)
	}
	return img
}

func saveImage(filename string, img image.Image) {
	f, err := os.Create(filename)
	if err != nil {
		log.Fatalf("os.Create failed: %v", err)
	}
	defer f.Close()
	err = png.Encode(f, img)
	if err != nil {
		log.Fatalf("png.Encode failed: %v", err)
	}
}

Feature Request: Arbitrary Rotation

I'd love to see something along the lines of php's imagerotate which allows arbitrary angles for rotation along with a parameter for the color of the padding. I haven't seen it in any go inserted manip libraries and it would be great!

Animated GIF transparency issue

Hi, I wasn't sure where the best place to post this was since I'm not sure if it's a bug or if I'm just doing something silly. If this is the wrong forum I apologise.

I've been working on a little HTTP API that builds on gift (https://github.com/paddycarey/ims) and it's working great for PNG and JPEG images, but I've run into an issue when applying filters to animated GIF images. It seems that transparency is not preserved in the filtered frames and it results in large black areas that should really be transparent since they haven't changed since the previous frame.

This is maybe easier to explain with an example.

package main

import (
    "fmt"
    "image"
    "image/gif"
    "os"
    "runtime"

    "github.com/disintegration/gift"
)

func main() {

    runtime.GOMAXPROCS(runtime.NumCPU())

    f, err := os.Open("test.gif")
    if err != nil {
        panic(err)
    }
    defer f.Close()

    g, err := gif.DecodeAll(f)
    if err != nil {
        panic(err)
    }

    filter := gift.New(
        gift.FlipHorizontal(),
    )

    newImages := []*image.Paletted{}
    for _, i := range g.Image {
        dst := image.NewPaletted(filter.Bounds(i.Bounds()), i.Palette)
        filter.Draw(dst, i)
        newImages = append(newImages, dst)
    }
    g.Image = newImages

    of, err := os.Create("out.gif")
    if err != nil {
        panic(err)
    }
    defer of.Close()

    err = gif.EncodeAll(of, g)
    if err != nil {
        panic(err)
    }

    fmt.Println("Successfully encoded gif: out.gif")
}

Used with the following test image:
test

Produces the following output:
out

I looked around and found some similar issues, I thought it may be the same as this issue, it's certainly got the same symptoms, but I'm not sure. I'm able to run DecodeAll and EncodeAll and the result will be fine, provided I don't use gift. For what it's worth I also tested with imaging and it has the same issue.

go version returns go1.4.1 linux/amd64 if that helps.

Thanks again for the great library, any help you could provide would be greatly appreciated.

Tests error on aarch64, ppc64le, s390x

Golang 1.12.6 on aarch64, ppc64le, s390x:

Testing    in: /builddir/build/BUILD/gift-1.2.0/_build/src
         PATH: /builddir/build/BUILD/gift-1.2.0/_build/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/sbin
       GOPATH: /builddir/build/BUILD/gift-1.2.0/_build:/usr/share/gocode
  GO111MODULE: off
      command: go test -buildmode pie -compiler gc -ldflags "-X github.com/disintegration/gift/version=1.2.0 -extldflags '-Wl,-z,relro -Wl,--as-needed  -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld '"
      testing: github.com/disintegration/gift
github.com/disintegration/gift
--- FAIL: TestBrightness (0.00s)
    colors_test.go:434: test [brightness (-30)] failed: image.Rectangle{Min:image.Point{X:0, Y:0}, Max:image.Point{X:5, Y:3}}, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x64, 0x54, 0x64, 0x14, 0x0, 0x34, 0x0, 0x34, 0x0}
--- FAIL: TestGolden (0.61s)
    gift_test.go:593: resulting image differs from golden: contrast_increase
    gift_test.go:593: resulting image differs from golden: saturation_decrease
    gift_test.go:593: resulting image differs from golden: saturation_increase
    gift_test.go:593: resulting image differs from golden: hue_rotate
FAIL

whiteboard cleaner

I stumble on gift yesterday and I was wondering if it is already at a point where I could use it to replace this one liner

#!/bin/bash
convert "$1" -morphology Convolve DoG:15,100,0 -negate -normalize -blur 0x1 -channel RBG -level 60%,91%,0.1 "$2"

If this is not the right place to ask question about this lib please let me know what is the best venue.

Thanks

Feature request: trim function

See the 'Trim' example in https://images.weserv.nl/#adjustments. It trims "boring" pixels from all edges that contain values within a similarity of the top-left pixel. So the excess of (for example) edged white space is removed. Useful to 'intelligently' crop an image of something against a even coloured background. The resulting image will be smaller but the informational part of the image will not change in any way.

Bad resize example in docs

In readme and docs:

gift.Resize(800, 0, gift.LanczosResampling),

It's probably a typo because resizing to height 0 isn't very useful.

How add custom dither filter with paletted image

I implemented gift.Filter interface with method Draw below. But the result plain is white image only. Is there something wrong with my code? Or How to implement gift.Filter in the right way?

func (f ditherFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
	dst = image.NewPaletted(src.Bounds(), color.Palette{color.Black, color.White})
	draw.FloydSteinberg.Draw(dst, src.Bounds(), src, image.ZP)
}

reference: https://golang.org/pkg/image/draw/

Thanks.

Progressive JPEG defaults to baseline after processing

Hi,

I am using gift with hugo. It is awesome, but I noticed that all jpegs processed end up as baseline.

Example:

  • original image:
    image

  • same image resized by hugo via gift:
    image

The tool used above can be found here.

Can this behavior be adjusted so that a progressive jpeg stays progressive after resize/filter?

Thank you!

Feature Request: Thresholding

Are there any plans to add thresholding to this library?

I'm completely new to this field, but would be interested in helping out if we can discuss what the API would look like. Any thoughts?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.