imagemagick

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 30, 2018 License: BSD-3-Clause Imports: 10 Imported by: 0

README

kamermans/imagemagick

Build Status Go Report Card Godoc

High-level Go wrapper for the ImageMagick convert command and a replacement for the identify command to gather detailed information on images like width, height, exif tags, colorspace, etc, without requiring the ImageMagick shared libraries; works on Linux, Windows, MacOS and any other system that has access to the convert command.

Check the godocs for the latest documentation: https://godoc.org/github.com/kamermans/imagemagick

Documentation

Overview

Package imagemagick provides a high-level wrapper for the ImageMagick `convert` command and a replacement for the `identify` command to gather detailed information on images like width, height, exif tags, colorspace, etc, without requiring the ImageMagick shared libraries; works on Linux, Windows, MacOS and any other system that has access to the `convert` command.

Example (GetImageDetails)

An example of getting image details from an image file. For this example we're downloading the file from w3.org, writing it to a temp file and getting its details

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"

	"github.com/kamermans/imagemagick"
)

// An example of getting image details from an image file.  For this example we're downloading the
// file from w3.org, writing it to a temp file and getting its details
func main() {

	var (
		convertCmd = `/usr/local/bin/convert`
		imageURL   = `https://www.w3.org/People/mimasa/test/imgformat/img/w3c_home.gif`
	)

	imageFile, err := downloadTempImage(imageURL)
	if err != nil {
		panic("Could not download the example image")
	}
	defer os.Remove(imageFile)

	parser := imagemagick.NewParser()
	parser.ConvertCommand = convertCmd
	results, detErr := parser.GetImageDetails(imageFile)
	if detErr != nil {
		panic(detErr.Error())
	}

	// Note that one output JSON can contain multiple results
	image := results[0].Image

	// Print the format
	fmt.Printf("Format: %v (%v)\n", image.Format, image.MimeType)

	// Print the geometry
	fmt.Printf("Dimensions: %v\n", *image.Geometry.Dimensions)

	// Example output:
	// Format: GIF (image/gif)
	// Dimensions: {Width: 72, Height: 48}

}

func downloadTempImage(imageURL string) (file string, err error) {
	resp, err := http.Get(imageURL)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	fh, err := ioutil.TempFile("", "imagemagick_example_")
	if err != nil {
		return
	}
	defer fh.Close()

	_, err = fh.Write(body)
	if err != nil {
		return
	}

	file = fh.Name()

	return
}
Example (GetImageDetailsFromJSON)

Example of getting image details from the ImageMagick `convert` tool's `json` format

package main

import (
	"fmt"
	"sort"

	"github.com/kamermans/imagemagick"
)

// Example of getting image details from the ImageMagick `convert` tool's `json` format
func main() {

	jsonBlob := getSampleJSONOutput()

	parser := imagemagick.NewParser()
	results, err := parser.GetImageDetailsFromJSON(jsonBlob)
	if err != nil {
		panic(err.Error())
	}

	// Note that one output JSON can contain multiple results
	image := results[0].Image

	// Print the filename
	fmt.Printf("Image: %v (%v)\n", image.BaseName, image.Format)

	// Collect the ICC information
	props := image.PropertiesMap()
	icc := props["icc"]
	// Sort ICC tags and print them in order so the tests don't fail :)
	propTags := []string{}
	for tag := range icc {
		propTags = append(propTags, tag)
	}
	sort.Strings(propTags)

	// Print the ICC information
	for _, tag := range propTags {
		fmt.Printf("ICC %v: %v\n", tag, icc[tag])
	}

	// Print the geometry
	fmt.Printf("Geometry: %v\n", *image.Geometry)

	// Print the profile size
	profileSizePct := image.ProfileSizePercent() * 100.0
	fmt.Printf("Profiles: The embedded profiles account for %.2f%% of the total file size\n", profileSizePct)

}

func getSampleJSONOutput() *[]byte {
	data := []byte(sampleJSONOutput)
	return &data
}

// This is an example of the output you get from ImageMagick `convert`
// when you use `json` for the output format.  For example, to get the
// JSON details for `foo.jpg`, you would use this command:
//
//	convert foo.jpg foo_details.json
//
// This package uses the STDOUT method to avoid writing an output file:
//
//	convert foo.jpg json:-
const sampleJSONOutput = `[{
"image": {
 "name": "json:/tmp/image_metadata_multi_formats_linux.json",
 "baseName": "bug72278.jpg",
 "format": "JPEG",
 "formatDescription": "JPEG",
 "mimeType": "image/jpeg",
 "class": "DirectClass",
 "geometry": {
    "width": 300,
    "height": 300,
    "x": 0,
    "y": 0
 },
 "resolution": {
    "x": 200,
    "y": 200
 },
 "printSize": {
    "x": 1.5,
    "y": 1.5
 },
 "units": "PixelsPerInch",
 "type": "Bilevel",
 "baseType": "Undefined",
 "endianess": "Undefined",
 "colorspace": "sRGB",
 "depth": 1,
 "baseDepth": 8,
 "channelDepth": {
    "red": 1,
    "green": 1,
    "blue": 1
 },
 "pixels": 270000,
 "imageStatistics": {
    "Overall": {
      "min": 255,
      "max": 255,
      "mean": 255,
      "standardDeviation": 0,
      "kurtosis": 1.6384e+64,
      "skewness": 9.375e+44,
      "entropy": -nan
    }
 },
 "channelStatistics": {
    "Red": {
      "min": 255,
      "max": 255,
      "mean": 255,
      "standardDeviation": 0,
      "kurtosis": 8.192e+63,
      "skewness": 1e+45,
      "entropy": -nan
    },
    "Green": {
      "min": 255,
      "max": 255,
      "mean": 255,
      "standardDeviation": 0,
      "kurtosis": 8.192e+63,
      "skewness": 1e+45,
      "entropy": -nan
    },
    "Blue": {
      "min": 255,
      "max": 255,
      "mean": 255,
      "standardDeviation": 0,
      "kurtosis": 8.192e+63,
      "skewness": 1e+45,
      "entropy": -nan
    }
 },
 "renderingIntent": "Perceptual",
 "gamma": 0.454545,
 "chromaticity": {
    "redPrimary": {
      "x": 0.64,
      "y": 0.33
    },
    "greenPrimary": {
      "x": 0.3,
      "y": 0.6
    },
    "bluePrimary": {
      "x": 0.15,
      "y": 0.06
    },
    "whitePrimary": {
      "x": 0.3127,
      "y": 0.329
    }
 },
 "matteColor": "#BDBDBD",
 "backgroundColor": "#FFFFFF",
 "borderColor": "#DFDFDF",
 "transparentColor": "#00000000",
 "interlace": "None",
 "intensity": "Undefined",
 "compose": "Over",
 "pageGeometry": {
    "width": 300,
    "height": 300,
    "x": 0,
    "y": 0
 },
 "dispose": "Undefined",
 "iterations": 0,
 "scene": 13,
 "scenes": 26,
 "compression": "None",
 "quality": 79,
 "orientation": "Undefined",
 "properties": {
    "comment": "Test",
    "date:create": "2017-10-19T10:30:02-04:00",
    "date:modify": "2017-10-19T10:30:02-04:00",
    "exif:ColorSpace": "1",
    "exif:ComponentsConfiguration": "1, 2, 3, 0",
    "exif:Copyright": "Test",
    "exif:DateTime": "2008:04:03 11:06:23",
    "exif:ExifImageLength": "300",
    "exif:ExifImageWidth": "300",
    "exif:ExifOffset": "196",
    "exif:ExifVersion": "48, 50, 50, 48",
    "exif:FlashPixVersion": "48, 49, 48, 48",
    "exif:ResolutionUnit": "2",
    "exif:Software": "Paint Shop Pro Photo 12.00",
    "exif:thumbnail:Compression": "6",
    "exif:thumbnail:JPEGInterchangeFormat": "380",
    "exif:thumbnail:JPEGInterchangeFormatLength": "1325",
    "exif:thumbnail:ResolutionUnit": "2",
    "exif:thumbnail:XResolution": "787399/10000",
    "exif:thumbnail:YCbCrPositioning": "2",
    "exif:thumbnail:YResolution": "787399/10000",
    "exif:XResolution": "1999995/10000",
    "exif:YCbCrPositioning": "2",
    "exif:YResolution": "1999995/10000",
    "icc:copyright": "Copyright (c) 1998 Hewlett-Packard Company",
    "icc:description": "sRGB IEC61966-2.1",
    "icc:manufacturer": "IEC http://www.iec.ch",
    "icc:model": "IEC 61966-2.1 Default RGB colour space - sRGB",
    "jpeg:colorspace": "2",
    "jpeg:sampling-factor": "1x1,1x1,1x1",
    "signature": "31fed455c2bb6e7258a946a2adc33d8493c2084346b9bb000e5042977c56221e"
 },
 "profiles": {
    "8bim": {
      "length": 28
    },
    "exif": {
      "length": 1717
    },
    "icc": {
      "length": 7261
    },
    "iptc": {
      "Unknown[2,0]": [null],
      "Copyright String[2,116]": ["Test"],
      "length": 16
    }
 },
 "tainted": false,
 "filesize": "45720B",
 "numberPixels": "90000",
 "pixelsPerSecond": "692308B",
 "userTime": "0.150u",
 "elapsedTime": "0:01.129",
 "version": "/usr/local/share/doc/ImageMagick-7//index.html"
}
}]`
Output:

Image: bug72278.jpg (JPEG)
ICC copyright: Copyright (c) 1998 Hewlett-Packard Company
ICC description: sRGB IEC61966-2.1
ICC manufacturer: IEC http://www.iec.ch
ICC model: IEC 61966-2.1 Default RGB colour space - sRGB
Geometry: {{X: 0, Y: 0} {Width: 300, Height: 300}}
Profiles: The embedded profiles account for 16.48% of the total file size
Example (GetImageDetailsParallel)

Example of getting the image details for all the files in a given directory in parallel, utilizing all the available CPUs in the machine. It also includes a progress function that shows the status of the job every 2 seconds. This example runs on Linux, Windows and MacOS

package main

import (
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"github.com/kamermans/imagemagick"
)

func main() {
	//func Test_getImageDetailsParallel(t *testing.T) {

	var (
		convertCmd    = `c:\ImageMagick\convert.exe`
		imageFilesDir = `c:\data\sample_images`
	)

	parser := imagemagick.NewParser()
	parser.ConvertCommand = convertCmd

	files := make(chan string)
	results := make(chan *imagemagick.ImageResult)
	errs := make(chan *imagemagick.ParserError)

	// Used to tell us when the results have all be consumed
	done := make(chan bool)

	parser.GetImageDetailsParallel(files, results, errs)

	// Send in files
	go func() {
		defer close(files)

		filepath.Walk(imageFilesDir, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				fmt.Printf("Unable to access path %q: %v\n", imageFilesDir, err)
				return err
			}

			if info.IsDir() {
				return nil
			}

			// Send this image into the files channel
			files <- path

			return nil
		})
	}()

	numErrors := 0
	numResults := 0
	startTime := time.Now()

	// Store the number of images of each format that we've seen
	resultsByFormat := map[string]int64{}
	// Store the total size of the images we've seen
	totalSize := int64(0)
	// Report progress this often
	reportInterval := 2 * time.Second

	// Report progress
	go func() {
		time.Sleep(reportInterval)
		for {
			// Get a sorted list of formats so it looks consistent
			formats := []string{}
			for format := range resultsByFormat {
				formats = append(formats, format)
			}
			sort.Strings(formats)

			outLines := []string{}
			for _, format := range formats {
				outLines = append(outLines, fmt.Sprintf("%v: %v", format, resultsByFormat[format]))
			}

			numPerSecond := float64(numResults+numErrors) / time.Since(startTime).Seconds()
			fmt.Printf("Results: %v, Errors: %v, Rate: %.0f/sec, Image Data: %v MB, Formats: {%v}\n",
				numResults,
				numErrors,
				numPerSecond,
				totalSize/1000000,
				strings.Join(outLines, ", "),
			)

			time.Sleep(reportInterval)
		}
	}()

	// Consume results and errors
	go func() {
		moreErrs := true
		moreResults := true
		for {
			if !moreErrs && !moreResults {
				break
			}

			select {
			case _, ok := <-errs:
				if !ok {
					moreErrs = false
					continue
				}
				numErrors++
			case details, ok := <-results:
				if !ok {
					moreResults = false
					continue
				}
				numResults++
				image := details.Image

				// Collect some stats for the progress function above
				totalSize += image.Size()
				if details.Image.Format != "" {
					resultsByFormat[details.Image.Format]++
				}

				// You can get the image details here if you want
				// fmt.Printf("Received result for image: %v (%v)\n",
				// 	image.BaseName,
				// 	image.Format,
				// )
			}
		}

		done <- true
	}()

	// Wait for all the results and errors to be consumed
	<-done

	fmt.Printf("Received %v results and %v errors\n", numResults, numErrors)

	// Here's what the output looks like on my laptop with 4523 sample images:
	//
	// Results: 40, Errors: 0, Rate: 20/sec, Image Data: 3 MB, Formats: {JPEG: 40}
	// Results: 160, Errors: 0, Rate: 40/sec, Image Data: 9 MB, Formats: {JPEG: 159, PNG: 1}
	// Results: 280, Errors: 0, Rate: 46/sec, Image Data: 18 MB, Formats: {JPEG: 279, PNG: 1}
	// ... lots of output ...
	// Results: 4304, Errors: 16, Rate: 46/sec, Image Data: 303 MB, Formats: {JPEG: 4281, PNG: 23}
	// Results: 4386, Errors: 17, Rate: 46/sec, Image Data: 305 MB, Formats: {JPEG: 4362, PNG: 24}
	// Results: 4465, Errors: 19, Rate: 46/sec, Image Data: 309 MB, Formats: {JPEG: 4440, PNG: 25}
	// Received 4503 results and 20 errors

}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ChannelStatistics

type ChannelStatistics struct {
	Min               float64 `json:"min"`               // 0,
	Max               float64 `json:"max"`               // 255,
	Mean              float64 `json:"mean"`              // 187.475,
	StandardDeviation float64 `json:"standardDeviation"` // 90.9415,
	Kurtosis          float64 `json:"kurtosis"`          // -1.22588,
	Skewness          float64 `json:"skewness"`          // -0.755169,
	Entropy           float64 `json:"entropy"`           // 0.515529
}

ChannelStatistics represents the image color channel statistics

type Dimensions

type Dimensions struct {
	Width  int64 `json:"width"`
	Height int64 `json:"height"`
}

Dimensions represents box dimensions with Width and Height

func (Dimensions) String

func (d Dimensions) String() string

String representation

type Geometry

type Geometry struct {
	*Point
	*Dimensions
}

Geometry represents image geometry, including a Point{X, Y} offset and Dimensions{Width, Height} dimensions

func (Geometry) Canvas

func (geo Geometry) Canvas() *Dimensions

Canvas is the total width and height of the canvas (width/height + x/y offset)

func (Geometry) Offset

func (geo Geometry) Offset() *Point

Offset coordinates of the box on the canvas

type ImageDetails

type ImageDetails struct {
	Alpha             string                            `json:"alpha"`               //"#00FF0000"
	BackgroundColor   string                            `json:"backgroundColor"`     //"#FFFFFF"
	BaseDepth         int64                             `json:"baseDepth"`           //8
	BaseName          string                            `json:"baseName"`            //"image_0002c93a9c0c53e7379a4524fa953ebb"
	BaseType          string                            `json:"baseType"`            //"Undefined"
	BorderColor       string                            `json:"borderColor"`         //"#DFDFDF"
	ChannelDepth      map[string]int64                  `json:"channelDepth"`        //
	ChannelStatistics map[string]*ChannelStatistics     `json:"channelStatistics"`   //
	Chromaticity      map[string]*PointFloat            `json:"chromaticity"`        //
	Class             string                            `json:"class"`               //"DirectClass"
	Colormap          []string                          `json:"colormap"`            //["#7F82B8FF","#393747FF"]
	ColormapEntries   int64                             `json:"colormapEntries"`     //128
	Colorspace        string                            `json:"colorspace"`          //"sRGB"
	Compose           string                            `json:"compose"`             //"Over"
	Compression       string                            `json:"compression"`         //"JPEG2000"
	Depth             int64                             `json:"depth"`               //8
	Dispose           string                            `json:"dispose"`             //"Undefined"
	ElapsedTime       string                            `json:"elapsedTime"`         //"0:01.049"
	Endianess         string                            `json:"endianess"`           //"Undefined"
	Filesize          string                            `json:"filesize"`            //"0B"
	Format            string                            `json:"format"`              //"JP2"
	FormatDescription string                            `json:"formatDescription"`   //"JP2"
	Gamma             float64                           `json:"gamma"`               //0.454545
	Geometry          *Geometry                         `json:"geometry"`            //
	ImageStatistics   map[string]*ChannelStatistics     `json:"imageStatistics"`     //
	Intensity         string                            `json:"intensity"`           //"Undefined"
	Interlace         string                            `json:"interlace"`           //"None"
	Iterations        int64                             `json:"iterations"`          //0
	MatteColor        string                            `json:"matteColor"`          //"#BDBDBD"
	MimeType          string                            `json:"mimeType"`            //"image/jp2"
	Name              string                            `json:"name"`                //"test.json"
	NumberPixels      int64                             `json:"numberPixels,string"` //"211750"
	Orientation       string                            `json:"orientation"`         //"Undefined"
	PageGeometry      *Geometry                         `json:"pageGeometry"`        //
	Pixels            int64                             `json:"pixels"`              //635250
	PixelsPerSecond   string                            `json:"pixelsPerSecond"`     //"4235000B"
	PrintSize         *PointFloat                       `json:"printSize"`           //{"x": 2.08333,"y": 1.04167}
	Profiles          map[string]map[string]interface{} `json:"profiles"`            //
	Properties        map[string]string                 `json:"properties"`          //
	Quality           int64                             `json:"quality"`             //75
	RenderingIntent   string                            `json:"renderingIntent"`     //"Perceptual"
	Resolution        *PointFloat                       `json:"resolution"`          //{"x": 96,"y": 96}
	Scene             int64                             `json:"scene"`               //12
	Scenes            int64                             `json:"scenes"`              //26
	Tainted           bool                              `json:"tainted"`             //false
	TransparentColor  string                            `json:"transparentColor"`    //"#00000000"
	Type              string                            `json:"type"`                //"TrueColor"
	Units             string                            `json:"units"`               //"Undefined"
	UserTime          string                            `json:"userTime"`            //"0.030u"
	Version           string                            `json:"version"`             //"/usr/local/share/doc/ImageMagick-7//index.html"
}

ImageDetails provides detailed information on the image, there are many helpful methods on this object

func (ImageDetails) ExifTags

func (details ImageDetails) ExifTags() map[string]string

ExifTags returns a map of EXIF tags to their values. These are pulled from the Properties slice. Note that the prefix "exif:" is timmed from the tag name. An empty map is returned if there are no EXIF tags present

func (ImageDetails) HasProfile

func (details ImageDetails) HasProfile(name string) bool

HasProfile returns true if the image has an embedded profile of the given type. Possible options include, but are not limited to: 8bim, exif, iptc, xmp, icc, app1, app12 Note that zero-length profiles will return false

func (ImageDetails) ProfileNames

func (details ImageDetails) ProfileNames() (names []string)

ProfileNames of the embedded profiles. Note that all profiles are included, even if they are zero-length

func (ImageDetails) ProfileSizePercent

func (details ImageDetails) ProfileSizePercent() float64

ProfileSizePercent returns the percentage of the total filesize which is used by the profiles

func (ImageDetails) ProfileSizes

func (details ImageDetails) ProfileSizes() (lengths map[string]int64)

ProfileSizes returns a map of embedded profile names to their size in bytes

func (ImageDetails) ProfileTotalSize

func (details ImageDetails) ProfileTotalSize() (size int64)

ProfileTotalSize returns the total byte size of all the embedded profiles

func (ImageDetails) PropertiesMap

func (details ImageDetails) PropertiesMap(tagFilter ...string) map[string]map[string]string

PropertiesMap returns a map of the image Properties. The key is split on the first ":" and grouped by the first half (the tag name) so the map is a map of map[string]string like this:

{
	"icc": {
		"brand": "Canon",
		"model": "EOS 5D Mark IV",
	},
	"exif": {
		"Software": "Adobe Photoshop CC 2017 (Macintosh)",
	},
}

func (ImageDetails) Size

func (details ImageDetails) Size() int64

Size of the image in bytes. ImageMagick returns a strangely-formatted string and this the in64 equivalent

func (*ImageDetails) ToJSON

func (details *ImageDetails) ToJSON(pretty bool) (out []byte, err error)

ToJSON returns the JSON representation of this object

type ImageResult

type ImageResult struct {
	Image *ImageDetails `json:"image"`
}

ImageResult is the top-Level result from ImageMagick. You almost certainly want to access the Image property, but this wrapper is left here for future use, should other types by introduced

func (*ImageResult) ToJSON

func (details *ImageResult) ToJSON(pretty bool) (out []byte, err error)

ToJSON returns the JSON representation of this object

type Parser

type Parser struct {
	// The 'convert' command
	ConvertCommand string
	// Number of files to pass to convert at once when running in parallel
	BatchSize int
	// Number of workers to start when running in parallel (default: # of CPUs)
	Workers int
	// contains filtered or unexported fields
}

Parser represents an ImageMagick command-line tool parser

func NewParser

func NewParser() *Parser

NewParser creates a new Parser object

func (*Parser) Convert

func (parser *Parser) Convert(args ...string) (stdOut *[]byte, stdErr *[]byte, err *ParserError)

Convert is a helper to call the ImageMagick `convert` command. It will return the stdOut, stdErr and a ParserError if the command failed (by returing a non-zero exit code, for example)

func (*Parser) GetImageDetails

func (parser *Parser) GetImageDetails(files ...string) (results []*ImageResult, err *ParserError)

GetImageDetails computes ImageDetails for one or more input files, returning (results, err). If an error is encountered, results will be nil and err will contain the error.

func (*Parser) GetImageDetailsFromJSON

func (parser *Parser) GetImageDetailsFromJSON(jsonBlob *[]byte) (results []*ImageResult, err error)

GetImageDetailsFromJSON computes ImageDetails for the given JSON data, returning (results, err). If an error is encountered, results will be nil and err will contain the error. Note that the JSON data is cleaned of invalid numbers with Regexp because ImageMagick `convert` leaks C++ NaNs into the output data, like `{"bytes": -nan}` and `{"entropy": -1.#IND}`

func (*Parser) GetImageDetailsParallel

func (parser *Parser) GetImageDetailsParallel(
	files <-chan string,
	results chan<- *ImageResult,
	errs chan<- *ParserError,
)

GetImageDetailsParallel computes ImageDetails for a channel of input files. The results are available in the results channel and errors are on the errors channel. You should read the results and errors channels in a go routine to prevent blocking. The number of workers is defined at Parser.Workers. ImageMagick supports batches of input files, and this function uses batches of size Parse.BatchSize. When a batch of files is passed to ImageMagick and an error is encountered, the batch is split up and each file is sent individually so the bad file can be identified and sent to the errors channel.

func (*Parser) SetCommand

func (parser *Parser) SetCommand(command func(name string, arg ...string) *exec.Cmd)

SetCommand allows you to set an alternate exec.Cmd object, which is useful for mocking commands for testing

type ParserError

type ParserError struct {
	// contains filtered or unexported fields
}

ParserError represents an error by the parser

func NewParserError

func NewParserError(msg string, file string, cmd string, stdOut []byte, stdErr []byte) *ParserError

NewParserError creates a new ParserError

func (*ParserError) Cmd

func (err *ParserError) Cmd() string

Cmd returns the command that caused the error (if any)

func (*ParserError) Error

func (err *ParserError) Error() string

Error returns a string representation of the error with all of its properties

func (*ParserError) File

func (err *ParserError) File() string

File returns the file that caused the error (if any)

func (*ParserError) Msg

func (err *ParserError) Msg() string

Msg returns the error message

func (*ParserError) StdErr

func (err *ParserError) StdErr() []byte

StdErr that was produced by the failed command (if any)

func (*ParserError) StdOut

func (err *ParserError) StdOut() []byte

StdOut that was produced by the failed command (if any)

type Point

type Point struct {
	X int64 `json:"x"`
	Y int64 `json:"y"`
}

Point represents an X, Y point / coordinate

func (Point) String

func (p Point) String() string

String representation

type PointFloat

type PointFloat struct {
	X float64 `json:"x"`
	Y float64 `json:"y"`
}

PointFloat represents a float64 X, Y point / coordinate

func (PointFloat) String

func (p PointFloat) String() string

String representation

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL