Skip to content

Commit

Permalink
feat(images): add images package with a bunch of features to handle i…
Browse files Browse the repository at this point in the history
…mage converting
  • Loading branch information
danvergara committed Nov 2, 2023
1 parent 3dc1430 commit 52b2ccb
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 0 deletions.
157 changes: 157 additions & 0 deletions pkg/files/images/images.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package images

import (
"bytes"
"fmt"
"image"
"image/gif"
"image/jpeg"
"image/png"
"strings"
)

const (
PNG = "png"
JPEG = "jpeg"
JPG = "jpg"
GIF = "gif"

imageMimeType = "image/"
)

// FileFormat is the file format representation meant to be shown in the
// form template as an option.
type FileFormat struct {
// Name of the file format to be shown in the option tag and as option value.
Name string
}

// ConverImage returns a image converted as an array of bytes,
// if somethings wrong happens, the functions will error out.
// The functions receives the format from to be converted,
// the file format to be converted to and the image to be converted.
func ConverImage(from, to string, imageBytes []byte) ([]byte, error) {
var (
img image.Image
result []byte
err error
)

to = ParseMimeType(to)
from = ParseMimeType(from)

switch from {
case PNG:
img, err = png.Decode(bytes.NewReader(imageBytes))
if err != nil {
return nil, err
}
case JPEG, JPG:
img, err = jpeg.Decode(bytes.NewReader(imageBytes))
if err != nil {
return nil, err
}
case GIF:
img, err = gif.Decode(bytes.NewReader(imageBytes))
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("file format %s not supported", from)
}

switch to {
case PNG:
result, err = toPNG(img)
if err != nil {
return nil, err
}
case JPEG, JPG:
result, err = toJPG(img)
if err != nil {
return nil, err
}
case GIF:
result, err = toGIF(img)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("file format to conver to %s not supported", to)
}

return result, nil
}

func toPNG(img image.Image) ([]byte, error) {
buf := new(bytes.Buffer)

// encode the image as a PNG file.
if err := png.Encode(buf, img); err != nil {
return nil, err
}

return buf.Bytes(), nil
}

func toGIF(img image.Image) ([]byte, error) {
buf := new(bytes.Buffer)

// encode the image as a GIF file.
if err := gif.Encode(buf, img, nil); err != nil {
return nil, err
}

return buf.Bytes(), nil
}

func toJPG(img image.Image) ([]byte, error) {
buf := new(bytes.Buffer)

// encode the image as a JPEG file.
if err := jpeg.Encode(buf, img, nil); err != nil {
return nil, err
}

return buf.Bytes(), nil
}

func FileFormatsToConvert(to string) map[string][]FileFormat {
formats := make(map[string][]FileFormat)

to = ParseMimeType(to)

switch to {
case JPEG, JPG:
formats = map[string][]FileFormat{
"Formats": {
{Name: PNG},
{Name: GIF},
},
}
case PNG:
formats = map[string][]FileFormat{
"Formats": {
{Name: JPG},
{Name: GIF},
},
}
case GIF:
formats = map[string][]FileFormat{
"Formats": {
{Name: JPG},
{Name: PNG},
},
}
}

return formats
}

func ParseMimeType(mimetype string) string {
if !strings.Contains(mimetype, imageMimeType) {
return mimetype
}

return strings.TrimPrefix(mimetype, imageMimeType)
}
131 changes: 131 additions & 0 deletions pkg/files/images/images_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package images_test

import (
"net/http"
"os"
"testing"

"github.com/danvergara/morphos/pkg/files/images"
"github.com/stretchr/testify/require"
)

func TestConvertImage(t *testing.T) {
type input struct {
filename string
mimetype string
targetFormat string
}
type expected struct {
mimetype string
}
var tests = []struct {
name string
input input
expected expected
}{
{
name: "png to jpeg",
input: input{
filename: "testdata/gopher_pirate.png",
mimetype: "image/png",
targetFormat: "jpeg",
},
expected: expected{
mimetype: "image/jpeg",
},
},
{
name: "jpeg to png",
input: input{
filename: "testdata/Golang_Gopher.jpg",
mimetype: "image/jpeg",
targetFormat: "png",
},
expected: expected{
mimetype: "image/png",
},
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
inputImg, err := os.ReadFile(tc.input.filename)
require.NoError(t, err)

detectedFileType := http.DetectContentType(inputImg)
require.Equal(t, tc.input.mimetype, detectedFileType)

convertedImg, err := images.ConverImage(detectedFileType, tc.input.targetFormat, inputImg)
require.NoError(t, err)

detectedFileType = http.DetectContentType(convertedImg)
require.Equal(t, tc.expected.mimetype, detectedFileType)
})
}

}

func TestFileFormatsToConvert(t *testing.T) {
type input struct {
format string
}
type expected struct {
targetFormats []images.FileFormat
}

var tests = []struct {
name string
input input
expected expected
}{
{
name: "JPEG",
input: input{
format: images.JPEG,
},
expected: expected{
targetFormats: []images.FileFormat{
{Name: images.PNG},
{Name: images.GIF},
},
},
},
{
name: "PNG",
input: input{
format: images.PNG,
},
expected: expected{
targetFormats: []images.FileFormat{
{Name: images.JPG},
{Name: images.GIF},
},
},
},
{
name: "GIF",
input: input{
format: images.GIF,
},
expected: expected{
targetFormats: []images.FileFormat{
{Name: images.JPG},
{Name: images.PNG},
},
},
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
formats := images.FileFormatsToConvert(tc.input.format)
require.EqualValues(t, tc.expected.targetFormats, formats["Formats"])
})
}
}

func TestParseMimeType(t *testing.T) {
parsedType := images.ParseMimeType("image/png")
require.Equal(t, parsedType, "png")
}
Binary file added pkg/files/images/testdata/Golang_Gopher.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 pkg/files/images/testdata/gopher_pirate.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 52b2ccb

Please sign in to comment.