-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(images): add images package with a bunch of features to handle i…
…mage converting
- Loading branch information
1 parent
3dc1430
commit 52b2ccb
Showing
4 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.