Skip to content

Commit 311df38

Browse files
committed
initial commit
0 parents  commit 311df38

File tree

9 files changed

+364
-0
lines changed

9 files changed

+364
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
main

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
run:
2+
go run *.go -o ./tmp
3+
build:
4+
go build -o image-compressor

README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Image Compressor API
2+
3+
## Overview
4+
5+
Image Compressor API is a simple HTTP service written in Go that allows you to compress and resize images from a given URL. It supports popular image formats such as JPEG, PNG, and WebP.
6+
7+
## Features
8+
9+
- Image compression and resizing based on provided parameters.
10+
- Automatic determination of the image format based on the URL's content type.
11+
- Support for JPEG, PNG, and WebP output formats.
12+
- Option to specify output quality and resolution.
13+
- Efficient caching: If the compressed image already exists, it is served without re-compression.
14+
15+
## Getting Started
16+
17+
### Prerequisites
18+
19+
- Go (Golang) installed on your machine.
20+
- [mux](https://github.com/gorilla/mux), [nfnt/resize](https://github.com/nfnt/resize), and [chai2010/webp](https://github.com/chai2010/webp) Go packages.
21+
22+
### Installation
23+
24+
1. Clone the repository:
25+
26+
```bash
27+
git clone https://github.com/yourusername/your-repo.git
28+
cd your-repo
29+
```
30+
31+
2. Install dependencies:
32+
33+
```bash
34+
go get -u github.com/gorilla/mux
35+
go get -u github.com/nfnt/resize
36+
go get -u github.com/chai2010/webp
37+
```
38+
39+
3. Build and run the project:
40+
41+
```bash
42+
go run *.go -o ./tmp
43+
```
44+
45+
Alternatively, for a production build:
46+
47+
```bash
48+
go build -o image-compressor
49+
./image-compressor -o ./tmp
50+
```
51+
52+
### Usage
53+
54+
To compress an image, make a GET request to the `/compressor` endpoint with the following parameters:
55+
56+
- `url`: URL of the image to be compressed.
57+
- `output`: Desired output format (e.g., "jpeg", "png", "webp").
58+
- `quality`: Output quality (0-100, applicable for JPEG).
59+
- `resolution`: Output resolution in the format "widthxheight" (e.g., "1024x720").
60+
61+
Example:
62+
63+
```bash
64+
curl "http://localhost:8080/compressor?url=https://example.com/image.jpg&output=webp&quality=80&resolution=1024x720"
65+
```
66+
67+
## API Endpoints
68+
69+
### `/compressor`
70+
71+
- **Method:** GET
72+
- **Parameters:**
73+
- `url` (required): URL of the image to be compressed.
74+
- `output` (optional): Desired output format (e.g., "jpeg", "png", "webp").
75+
- `quality` (optional): Output quality (0-100, applicable for JPEG).
76+
- `resolution` (optional): Output resolution in the format "widthxheight" (e.g., "1024x720").
77+
78+
Example:
79+
80+
```bash
81+
curl "http://localhost:8080/compressor?url=https://example.com/image.jpg&output=webp&quality=80&resolution=1024x720"
82+
```
83+
84+
## License
85+
86+
This project is licensed under the [MIT License](LICENSE).

go.mod

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module github.com/daniwebdev/image-compressor-api
2+
3+
go 1.20
4+
5+
require (
6+
github.com/chai2010/webp v1.1.1 // indirect
7+
github.com/gorilla/mux v1.8.1 // indirect
8+
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
9+
)

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
2+
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
3+
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
4+
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
5+
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
6+
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=

image-compressor

8.75 MB
Binary file not shown.

main.go

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
package main
2+
3+
import (
4+
"crypto/md5"
5+
"encoding/hex"
6+
"flag"
7+
"fmt"
8+
"image"
9+
"image/jpeg"
10+
"image/png"
11+
"io"
12+
"net/http"
13+
"os"
14+
"path/filepath"
15+
"strings"
16+
17+
"github.com/chai2010/webp"
18+
"github.com/gorilla/mux"
19+
"github.com/nfnt/resize"
20+
)
21+
22+
var outputDirectory string
23+
24+
func init() {
25+
flag.StringVar(&outputDirectory, "o", ".", "Output directory for compressed images")
26+
flag.Parse()
27+
}
28+
29+
func downloadImage(url string) (image.Image, string, error) {
30+
resp, err := http.Get(url)
31+
if err != nil {
32+
return nil, "", err
33+
}
34+
defer resp.Body.Close()
35+
36+
var img image.Image
37+
var format string
38+
39+
// Determine the image format based on content type
40+
contentType := resp.Header.Get("Content-Type")
41+
switch {
42+
case strings.Contains(contentType, "jpeg"):
43+
img, _, err = image.Decode(resp.Body)
44+
format = "jpeg"
45+
case strings.Contains(contentType, "png"):
46+
img, _, err = image.Decode(resp.Body)
47+
format = "png"
48+
case strings.Contains(contentType, "webp"):
49+
img, err = webp.Decode(resp.Body)
50+
format = "webp"
51+
default:
52+
return nil, "", fmt.Errorf("unsupported image format")
53+
}
54+
55+
if err != nil {
56+
return nil, "", err
57+
}
58+
59+
return img, format, nil
60+
}
61+
62+
func compressImage(img image.Image, format, output string, quality int, resolution string) error {
63+
// Resize the image if resolution is provided
64+
if resolution != "" {
65+
size := strings.Split(resolution, "x")
66+
width, height := parseResolution(size[0], size[1], img.Bounds().Dx(), img.Bounds().Dy())
67+
img = resize.Resize(width, height, img, resize.Lanczos3)
68+
}
69+
70+
// Create the output file in the specified directory
71+
out, err := os.Create(filepath.Join(outputDirectory, output))
72+
if err != nil {
73+
return err
74+
}
75+
defer out.Close()
76+
77+
// Compress and save the image in the specified format
78+
switch format {
79+
case "jpeg":
80+
options := jpeg.Options{Quality: quality}
81+
err = jpeg.Encode(out, img, &options)
82+
case "png":
83+
encoder := png.Encoder{CompressionLevel: png.BestCompression}
84+
err = encoder.Encode(out, img)
85+
case "webp":
86+
options := &webp.Options{Lossless: true}
87+
err = webp.Encode(out, img, options)
88+
default:
89+
return fmt.Errorf("unsupported output format")
90+
}
91+
92+
if err != nil {
93+
return err
94+
}
95+
96+
return nil
97+
}
98+
99+
func generateMD5Hash(input string) string {
100+
hasher := md5.New()
101+
hasher.Write([]byte(input))
102+
return hex.EncodeToString(hasher.Sum(nil))
103+
}
104+
105+
func atoi(s string) int {
106+
result := 0
107+
for _, c := range s {
108+
result = result*10 + int(c-'0')
109+
}
110+
return result
111+
}
112+
113+
func parseResolution(width, height string, originalWidth, originalHeight int) (uint, uint) {
114+
var newWidth, newHeight uint
115+
116+
if width == "auto" && height == "auto" {
117+
// If both dimensions are "auto," maintain the original size
118+
newWidth = uint(originalWidth)
119+
newHeight = uint(originalHeight)
120+
} else if width == "auto" {
121+
// If width is "auto," calculate height maintaining the aspect ratio
122+
ratio := float64(originalWidth) / float64(originalHeight)
123+
newHeight = uint(atoi(height))
124+
newWidth = uint(float64(newHeight) * ratio)
125+
} else if height == "auto" {
126+
// If height is "auto," calculate width maintaining the aspect ratio
127+
ratio := float64(originalHeight) / float64(originalWidth)
128+
newWidth = uint(atoi(width))
129+
newHeight = uint(float64(newWidth) * ratio)
130+
} else {
131+
// Use the provided width and height
132+
newWidth = uint(atoi(width))
133+
newHeight = uint(atoi(height))
134+
}
135+
136+
return newWidth, newHeight
137+
}
138+
139+
func compressHandler(w http.ResponseWriter, r *http.Request) {
140+
url := r.URL.Query().Get("url")
141+
format := r.URL.Query().Get("output")
142+
quality := r.URL.Query().Get("quality")
143+
resolution := r.URL.Query().Get("resolution")
144+
145+
// Concatenate parameters into a single string
146+
paramsString := fmt.Sprintf("%s-%s-%s-%s", url, format, quality, resolution)
147+
148+
// Generate MD5 hash from the concatenated parameters
149+
hash := generateMD5Hash(paramsString)
150+
151+
// Generate the output filename using the hash and format
152+
output := fmt.Sprintf("%s.%s", hash, format)
153+
154+
// Check if the compressed file already exists in the output directory
155+
filePath := filepath.Join(outputDirectory, output)
156+
if _, err := os.Stat(filePath); err == nil {
157+
// File exists, no need to download and compress again
158+
159+
// Open and send the existing compressed image file
160+
compressedFile, err := os.Open(filePath)
161+
if err != nil {
162+
http.Error(w, fmt.Sprintf("Error opening compressed image file: %s", err), http.StatusInternalServerError)
163+
return
164+
}
165+
defer compressedFile.Close()
166+
167+
// Set the appropriate Content-Type based on the output format
168+
var contentType string
169+
switch format {
170+
case "jpeg":
171+
contentType = "image/jpeg"
172+
case "png":
173+
contentType = "image/png"
174+
case "webp":
175+
contentType = "image/webp"
176+
default:
177+
http.Error(w, "Unsupported output format", http.StatusInternalServerError)
178+
return
179+
}
180+
181+
// Set the Content-Type header
182+
w.Header().Set("Content-Type", contentType)
183+
184+
// Copy the existing compressed image file to the response writer
185+
_, err = io.Copy(w, compressedFile)
186+
if err != nil {
187+
http.Error(w, fmt.Sprintf("Error sending compressed image: %s", err), http.StatusInternalServerError)
188+
return
189+
}
190+
191+
return
192+
}
193+
194+
img, imgFormat, err := downloadImage(url)
195+
if err != nil {
196+
http.Error(w, fmt.Sprintf("Error downloading image: %s", err), http.StatusInternalServerError)
197+
return
198+
}
199+
200+
if format == "" {
201+
format = imgFormat
202+
}
203+
204+
err = compressImage(img, format, output, atoi(quality), resolution)
205+
if err != nil {
206+
http.Error(w, fmt.Sprintf("Error compressing image: %s", err), http.StatusInternalServerError)
207+
return
208+
}
209+
210+
// Set the appropriate Content-Type based on the output format
211+
var contentType string
212+
switch format {
213+
case "jpeg":
214+
contentType = "image/jpeg"
215+
case "png":
216+
contentType = "image/png"
217+
case "webp":
218+
contentType = "image/webp"
219+
default:
220+
http.Error(w, "Unsupported output format", http.StatusInternalServerError)
221+
return
222+
}
223+
224+
// Set the Content-Type header
225+
w.Header().Set("Content-Type", contentType)
226+
227+
// Open and send the compressed image file
228+
compressedFile, err := os.Open(filePath)
229+
if err != nil {
230+
http.Error(w, fmt.Sprintf("Error opening compressed image file: %s", err), http.StatusInternalServerError)
231+
return
232+
}
233+
defer compressedFile.Close()
234+
235+
// Copy the compressed image file to the response writer
236+
_, err = io.Copy(w, compressedFile)
237+
if err != nil {
238+
http.Error(w, fmt.Sprintf("Error sending compressed image: %s", err), http.StatusInternalServerError)
239+
return
240+
}
241+
fmt.Fprintf(w, "Image compressed and saved to %s\n", filePath)
242+
}
243+
244+
245+
246+
func main() {
247+
r := mux.NewRouter()
248+
r.HandleFunc("/compressor", compressHandler).Methods("GET")
249+
r.HandleFunc("/compressor/{filename}", compressHandler).Methods("GET")
250+
251+
http.Handle("/", r)
252+
253+
fmt.Printf("Server is listening on :8080. Output directory: %s\n", outputDirectory)
254+
http.ListenAndServe(":8080", nil)
255+
}

tmp/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*
2+
!README.md
3+
!.gitignore

tmp/README.md

Whitespace-only changes.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy