Compare commits

..

61 commits

Author SHA1 Message Date
cf5254ad5f Don't build to arm anymore
Some checks failed
continuous-integration/drone/push Build is failing
2026-01-07 03:22:59 +00:00
afc05c5419 Fix build on arm
All checks were successful
continuous-integration/drone/push Build is passing
2023-01-22 20:30:24 +01:00
e72a787a59 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.136
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-11 21:25:25 +00:00
dd76f7753c chore(deps): update module github.com/aws/aws-sdk-go to v1.44.135
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-10 20:26:19 +00:00
28155ce861 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.133
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-08 20:25:47 +00:00
51e06b4e8c chore(deps): update module github.com/aws/aws-sdk-go to v1.44.132
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-11-07 20:23:30 +00:00
bfc360dfda chore(deps): update module github.com/aws/aws-sdk-go to v1.44.130
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-03 19:24:28 +00:00
6078a885fb chore(deps): update module github.com/aws/aws-sdk-go to v1.44.129
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-02 19:22:06 +00:00
d7ce810fdf chore(deps): update module github.com/aws/aws-sdk-go to v1.44.128
All checks were successful
continuous-integration/drone/push Build is passing
2022-11-01 19:23:35 +00:00
f1081e7805 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.127
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-31 19:24:11 +00:00
77309af9a4 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.126
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-28 19:23:33 +00:00
9bdad10889 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.125
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-27 20:24:00 +00:00
6177eb693a chore(deps): update module github.com/aws/aws-sdk-go to v1.44.124
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-26 19:21:17 +00:00
aeb006ce8c chore(deps): update module github.com/aws/aws-sdk-go to v1.44.123
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-25 19:23:40 +00:00
4d7a596556 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.122
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-24 20:23:17 +00:00
b31cb19326 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.121
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-21 21:21:55 +00:00
cec1160a09 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.120
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-20 19:32:14 +00:00
1fb6dfa3e6 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.119
Some checks failed
continuous-integration/drone/push Build is failing
2022-10-19 19:24:03 +00:00
c94b7de365 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.118
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-18 19:23:20 +00:00
dc4ea96e40 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.117
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-17 19:23:40 +00:00
705d28ef4a chore(deps): update module github.com/aws/aws-sdk-go to v1.44.116
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-14 19:27:47 +00:00
6b58cf4a3b chore(deps): update module github.com/aws/aws-sdk-go to v1.44.115
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-10-14 16:30:16 +00:00
9a46f6ee7b chore(deps): update module github.com/aws/aws-sdk-go to v1.44.109
Some checks failed
continuous-integration/drone/push Build is failing
2022-10-01 10:13:42 +00:00
819294659c chore(deps): update module github.com/aws/aws-sdk-go to v1.44.105
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-23 19:33:07 +00:00
4952b6f485 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.104
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-22 18:36:00 +00:00
dafb75337c chore(deps): update module github.com/aws/aws-sdk-go to v1.44.103
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-21 19:34:06 +00:00
8b4d44cbc6 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.102
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-20 19:32:26 +00:00
531c1a35c8 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.101
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-19 18:37:26 +00:00
f4ee510878 chore(deps): update module github.com/aws/aws-sdk-go to v1.44.100
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-16 19:33:16 +00:00
064b72b13e chore(deps): update module github.com/aws/aws-sdk-go to v1.44.99
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-16 13:41:17 +00:00
0710a3f77e chore(deps): update module github.com/aws/aws-sdk-go to v1.44.98
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-14 19:28:46 +00:00
8c36c3ff6e chore(deps): update module github.com/aws/aws-sdk-go to v1.44.97
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-13 19:34:37 +00:00
f48d9c463a chore(deps): update module github.com/aws/aws-sdk-go to v1.44.96
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-12 19:33:56 +00:00
ef9cec5b4a chore(deps): update module github.com/aws/aws-sdk-go to v1.44.95
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-09-10 22:42:28 +00:00
0c4a9e4935 renovate: Auto-merge aws-sdk-go updates
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-10 23:04:41 +02:00
ecda17fc7d Automatically select s3 backend storage if S3_xxx env are presents
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-07 11:48:28 +02:00
a2903b73a0 Add a log entry about which storage backend is in use
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-07 11:37:13 +02:00
3c14a3685c Fix launch problem when directories doesn't exist
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-07 10:43:13 +02:00
15be0cf465 Also publish to registry.nemunai.re
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-05 21:41:08 +02:00
f008669bd8 Handle option to switch between local and s3
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-04 23:32:03 +02:00
f1d98b605f Add s3 backend
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-04 23:15:16 +02:00
d4764ce485 Avoid infinite loop when no picture to load 2022-09-04 23:15:00 +02:00
4fcf12dff2 Huge rework to more modern code 2022-09-04 23:15:00 +02:00
6564d9c4fa Upgrade to go 1.16 and use embed module 2022-09-02 17:22:00 +02:00
187d71cb5b Enable image squash
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-23 09:35:40 +02:00
9d031ea9f9 Refactor admin
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-15 18:13:19 +02:00
f707954bd0 CD: Add docker image manifest 2021-09-15 18:13:19 +02:00
db8a42eebf Add renovate.json
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2021-08-03 09:10:10 +00:00
2cc038968f Add drone CI
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-18 19:59:11 +02:00
5f62fdef66 Add drone CI
Some checks reported errors
continuous-integration/drone/push Build was killed
2020-10-18 18:32:36 +02:00
80dca4434d Fix use of path given|
by -static when compiled in dev
2019-10-16 16:00:57 +02:00
deb4ff03ca In production mode, embed static content 2019-10-16 03:12:33 +02:00
0876fc3c2e Use the Fetch API instead of PrototypeJS 2019-10-16 03:12:33 +02:00
c622ada0df Add go module 2019-10-16 03:01:46 +02:00
dd8305ec06 Move go-crypt to gitlab 2019-10-16 03:01:32 +02:00
3dd22ecb09 In production mode, embed static content 2019-09-02 15:41:37 +02:00
d5fd91902f Use the Fetch API instead of PrototypeJS 2019-09-01 17:13:03 +02:00
b08ad493a9 If no htpasswd provided, publish without futher moderation 2018-10-04 02:02:45 +02:00
b3351cb3c9 Use base64 to upload image
So you can use:
    base64 mangemoi.jpg | curl --data @- http://localhost:8080/api/images/mangemoi
2018-10-04 02:01:29 +02:00
bd76956b46 format + bump 2018-10-04 01:20:41 +02:00
4febf49b3e gitignore 2018-10-04 01:20:41 +02:00
46 changed files with 1114 additions and 10855 deletions

22
.drone-manifest-local.yml Normal file
View file

@ -0,0 +1,22 @@
image: registry.nemunai.re/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
- image: registry.nemunai.re/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
platform:
architecture: amd64
os: linux
- image: registry.nemunai.re/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
platform:
architecture: arm64
os: linux
variant: v8
- image: registry.nemunai.re/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
platform:
architecture: arm
os: linux
variant: v7

22
.drone-manifest.yml Normal file
View file

@ -0,0 +1,22 @@
image: nemunaire/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
- image: nemunaire/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
platform:
architecture: amd64
os: linux
- image: nemunaire/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
platform:
architecture: arm64
os: linux
variant: v8
- image: nemunaire/youp0m:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
platform:
architecture: arm
os: linux
variant: v7

130
.drone.yml Normal file
View file

@ -0,0 +1,130 @@
---
kind: pipeline
type: docker
name: build-arm64
platform:
os: linux
arch: arm64
steps:
- name: build
image: golang:alpine
commands:
- apk --no-cache add git go-bindata
- go generate -v
- go get -v -d
- go build -v -ldflags="-s -w" -o youp0m
- wget -O Dockerfile https://ankh.serekh.nemunai.re/local/Dockerfile-youp0m
- wget -O entrypoint.sh https://ankh.serekh.nemunai.re/local/entrypoint.sh-youp0m && chmod +x entrypoint.sh
- name: publish
image: plugins/docker
settings:
repo: nemunaire/youp0m
auto_tag: true
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
experimental: true
squash: true
username:
from_secret: docker_username
password:
from_secret: docker_password
- name: publish on nemunai.re
image: plugins/docker
settings:
registry: registry.nemunai.re
repo: registry.nemunai.re/youp0m
auto_tag: true
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
experimental: true
squash: true
username:
from_secret: docker_nemunai.re_username
password:
from_secret: docker_nemunai.re_password
---
kind: pipeline
type: docker
name: build-amd64
platform:
os: linux
arch: amd64
steps:
- name: build
image: golang:alpine
commands:
- apk --no-cache add git go-bindata
- go generate -v
- go get -v -d
- go build -v -ldflags="-s -w" -o youp0m
- wget -O Dockerfile https://ankh.serekh.nemunai.re/local/Dockerfile-youp0m
- wget -O entrypoint.sh https://ankh.serekh.nemunai.re/local/entrypoint.sh-youp0m && chmod +x entrypoint.sh
- name: publish
image: plugins/docker
settings:
repo: nemunaire/youp0m
auto_tag: true
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
experimental: true
squash: true
username:
from_secret: docker_username
password:
from_secret: docker_password
- name: publish on nemunai.re
image: plugins/docker
settings:
registry: registry.nemunai.re
repo: registry.nemunai.re/youp0m
auto_tag: true
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
experimental: true
squash: true
username:
from_secret: docker_nemunai.re_username
password:
from_secret: docker_nemunai.re_password
---
kind: pipeline
name: docker-manifest
steps:
- name: publish
image: plugins/manifest
settings:
auto_tag: true
ignore_missing: true
spec: .drone-manifest.yml
username:
from_secret: docker_username
password:
from_secret: docker_password
- name: publish on nemunai.re
image: plugins/manifest
settings:
auto_tag: true
ignore_missing: true
spec: .drone-manifest-local.yml
username:
from_secret: docker_nemunai.re_username
password:
from_secret: docker_nemunai.re_password
trigger:
event:
- cron
- push
- tag
depends_on:
- build-arm64
- build-amd64

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
youp0m
vendor

34
api.go
View file

@ -12,21 +12,31 @@ import (
type DispatchFunction func(*User, []string, io.ReadCloser) (interface{}, error)
var apiRoutes = map[string]*(map[string]struct{AuthFunction;DispatchFunction}){
"images": &ApiImagesRouting,
"next": &ApiNextImagesRouting,
"version": &ApiVersionRouting,
}
type apiHandler struct {
Authenticate func(*http.Request) (*User)
PE *PictureExplorer
Authenticate func(*http.Request) *User
routes map[string](map[string]struct {
AuthFunction
DispatchFunction
})
}
func ApiHandler(Authenticate func(*http.Request) (*User)) (apiHandler) {
return apiHandler{Authenticate}
func NewAPIHandler(pe *PictureExplorer, authenticate func(*http.Request) *User) *apiHandler {
return &apiHandler{
pe,
authenticate,
map[string](map[string]struct {
AuthFunction
DispatchFunction
}){
"images": ApiImagesRouting(pe),
"next": ApiNextImagesRouting(pe),
"version": ApiVersionRouting,
},
}
}
func (a apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (a *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
user := a.Authenticate(r)
log.Printf("Handling %s API request from %s: %s %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, user, r.UserAgent())
@ -52,8 +62,8 @@ func (a apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Route request
if len(sURL) == 0 {
err = errors.New(fmt.Sprintf("No action provided"))
} else if h, ok := apiRoutes[sURL[0]]; ok {
if f, ok := (*h)[r.Method]; ok {
} else if h, ok := a.routes[sURL[0]]; ok {
if f, ok := h[r.Method]; ok {
if f.AuthFunction(user, sURL[1:]) {
ret, err = f.DispatchFunction(user, sURL[1:], r.Body)
} else {

View file

@ -5,72 +5,88 @@ import (
"io"
)
var ApiImagesRouting = map[string]struct{AuthFunction; DispatchFunction}{
"GET": {PublicPage, listImages},
"POST": {PublicPage, addImage},
"DELETE": {PrivatePage, hideImage},
}
var ApiNextImagesRouting = map[string]struct{AuthFunction; DispatchFunction}{
"GET": {PrivatePage, listNextImages},
"POST": {PublicPage, addImage},
"DELETE": {PrivatePage, deleteImage},
}
func listImages(u *User, args []string, body io.ReadCloser) (interface{}, error) {
if len(args) < 1 {
return GetPublishedImages()
} else if args[0] == "last" {
return GetLastImage()
} else {
return GetPublishedImage(args[0])
func ApiImagesRouting(pe *PictureExplorer) map[string]struct {
AuthFunction
DispatchFunction
} {
return map[string]struct {
AuthFunction
DispatchFunction
}{
"GET": {PublicPage, pe.listImages},
"POST": {PublicPage, pe.addImage},
"DELETE": {PrivatePage, pe.hideImage},
}
}
func listNextImages(u *User, args []string, body io.ReadCloser) (interface{}, error) {
func ApiNextImagesRouting(pe *PictureExplorer) map[string]struct {
AuthFunction
DispatchFunction
} {
return map[string]struct {
AuthFunction
DispatchFunction
}{
"GET": {PrivatePage, pe.listNextImages},
"POST": {PublicPage, pe.addImage},
"DELETE": {PrivatePage, pe.deleteImage},
}
}
func (pe *PictureExplorer) listImages(u *User, args []string, body io.ReadCloser) (interface{}, error) {
if len(args) < 1 {
return GetNextImages()
return pe.GetPublishedImages()
} else if args[0] == "last" {
return pe.GetLastImage()
} else {
return pe.GetPublishedImage(args[0])
}
}
func (pe *PictureExplorer) listNextImages(u *User, args []string, body io.ReadCloser) (interface{}, error) {
if len(args) < 1 {
return pe.GetNextImages()
} else if len(args) >= 2 && args[1] == "publish" {
if pict, err := GetNextImage(args[0]); err != nil {
if pict, err := pe.GetNextImage(args[0]); err != nil {
return nil, err
} else if err := pict.Publish(); err != nil {
} else if err := pe.Publish(pict); err != nil {
return nil, err
} else {
return true, nil
}
} else {
return GetNextImage(args[0])
return pe.GetNextImage(args[0])
}
}
func addImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
func (pe *PictureExplorer) addImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
if len(args) < 1 {
return nil, errors.New("Need an image identifier to create")
} else if err := AddImage(args[0], body); err != nil {
} else if err := pe.AddImage(args[0], body); err != nil {
return nil, err
} else {
return true, nil
}
}
func hideImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
func (pe *PictureExplorer) hideImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
if len(args) < 1 {
return nil, errors.New("Need an image identifier to delete")
} else if pict, err := GetPublishedImage(args[0]); err != nil {
} else if pict, err := pe.GetPublishedImage(args[0]); err != nil {
return nil, errors.New("No matching image")
} else if err := pict.Unpublish(); err != nil {
} else if err := pe.Unpublish(pict); err != nil {
return nil, err
} else {
return true, nil
}
}
func deleteImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
func (pe *PictureExplorer) deleteImage(u *User, args []string, body io.ReadCloser) (interface{}, error) {
if len(args) < 1 {
return nil, errors.New("Need an image identifier to delete")
} else if pict, err := GetNextImage(args[0]); err != nil {
} else if pict, err := pe.GetNextImage(args[0]); err != nil {
return nil, errors.New("No matching image")
} else if err := pict.Remove(); err != nil {
} else if err := pe.Remove(pict); err != nil {
return nil, err
} else {
return true, nil

32
assets-dev.go Normal file
View file

@ -0,0 +1,32 @@
//go:build dev
// +build dev
package main
import (
"flag"
"net/http"
"os"
"path/filepath"
)
var (
Assets http.FileSystem
StaticDir string = "static/"
)
func init() {
flag.StringVar(&StaticDir, "static", StaticDir, "Directory containing static files")
}
func sanitizeStaticOptions() error {
StaticDir, _ = filepath.Abs(StaticDir)
if _, err := os.Stat(StaticDir); os.IsNotExist(err) {
StaticDir, _ = filepath.Abs(filepath.Join(filepath.Dir(os.Args[0]), "static"))
if _, err := os.Stat(StaticDir); os.IsNotExist(err) {
return err
}
}
Assets = http.Dir(StaticDir)
return nil
}

28
assets.go Normal file
View file

@ -0,0 +1,28 @@
//go:build !dev
// +build !dev
package main
import (
"embed"
"io/fs"
"log"
"net/http"
)
//go:embed static/* static/js/* static/css/*
var _assets embed.FS
var Assets http.FileSystem
func init() {
sub, err := fs.Sub(_assets, "static")
if err != nil {
log.Fatal("Unable to cd to static/ directory:", err)
}
Assets = http.FS(sub)
}
func sanitizeStaticOptions() error {
return nil
}

View file

@ -6,7 +6,7 @@ import (
"os"
"strings"
"github.com/nyarla/go-crypt"
"gitlab.com/nyarla/go-crypt"
)
type User struct {
@ -52,10 +52,9 @@ func (h Htpasswd) Authenticate(username, password string) *User {
}
}
/// Request authentication
func Authenticate(htpasswd Htpasswd, r *http.Request) (*User) {
func Authenticate(htpasswd Htpasswd, r *http.Request) *User {
// Authenticate the user if any
if username, password, ok := r.BasicAuth(); ok {
return htpasswd.Authenticate(username, password)
@ -64,7 +63,6 @@ func Authenticate(htpasswd Htpasswd, r *http.Request) (*User) {
}
}
/// Page rules
type AuthFunction func(*User, []string) bool

44
backend.go Normal file
View file

@ -0,0 +1,44 @@
package main
import (
"fmt"
"image"
"io"
"net/http"
"strings"
)
var existing_backends []string
type BackendSelector string
func (s *BackendSelector) String() string {
return string(*s)
}
func (s *BackendSelector) Set(v string) error {
found := false
for _, b := range existing_backends {
if strings.ToLower(v) == b {
found = true
tmp := BackendSelector(v)
s = &tmp
break
}
}
if !found {
return fmt.Errorf("%q is not a known file backend (existing backends: %v)", v, existing_backends)
}
return nil
}
type FileBackend interface {
DeletePicture(string, string) error
ServeFile() http.Handler
GetPicture(string, string, io.Writer) error
GetPictureInfo(string, string) (*Picture, error)
ListPictures(string) ([]*Picture, error)
MovePicture(string, string, string) error
PutPicture(string, string, *image.Image) error
}

106
backend_local.go Normal file
View file

@ -0,0 +1,106 @@
package main
import (
"image"
"image/jpeg"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"time"
)
func init() {
existing_backends = append(existing_backends, "local")
}
type LocalFileBackend struct {
BaseDir string
}
func (l *LocalFileBackend) getPath(box, name string) string {
return path.Join(l.BaseDir, box, name+".jpg")
}
func (l *LocalFileBackend) DeletePicture(box, name string) error {
return os.Remove(l.getPath(box, name))
}
func (l *LocalFileBackend) ServeFile() http.Handler {
return http.FileServer(http.Dir(l.BaseDir))
}
func (l *LocalFileBackend) createPictureInfo(local_path, name string, file os.FileInfo) *Picture {
return &Picture{
local_path, // Path
name, // Basename
name[:len(name)-len(path.Ext(name))], // Sanitized filename
file.ModTime(), // UploadTime
}
}
func (l *LocalFileBackend) GetPicture(box, name string, w io.Writer) error {
local_path := l.getPath(box, name)
fd, err := os.Open(local_path)
if err != nil {
return err
}
defer fd.Close()
_, err = io.Copy(w, fd)
return err
}
func (l *LocalFileBackend) GetPictureInfo(box, name string) (*Picture, error) {
local_path := l.getPath(box, name)
file, err := os.Stat(local_path)
if err != nil {
return nil, err
}
return l.createPictureInfo(path.Join(box, name+".jpg"), name, file), nil
}
func (l *LocalFileBackend) ListPictures(box string) ([]*Picture, error) {
files, err := ioutil.ReadDir(path.Join(l.BaseDir, box))
if err != nil {
return nil, err
}
pictures := make([]*Picture, 0)
for _, file := range files {
if !file.IsDir() {
pictures = append(pictures, l.createPictureInfo(path.Join(box, file.Name()), file.Name(), file))
}
}
return pictures, nil
}
func (l *LocalFileBackend) MovePicture(box_from, box_to, name string) error {
newpath := l.getPath(box_to, name)
err := os.Rename(
l.getPath(box_from, name),
newpath,
)
if err != nil {
return err
}
return os.Chtimes(newpath, time.Now(), time.Now())
}
func (l *LocalFileBackend) PutPicture(box, name string, img *image.Image) error {
fd, err := os.OpenFile(l.getPath(box, name), os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
return err
}
defer fd.Close()
return jpeg.Encode(fd, *img, nil)
}

272
backend_s3.go Normal file
View file

@ -0,0 +1,272 @@
package main
import (
"bytes"
"flag"
"fmt"
"image"
"image/jpeg"
"io"
"log"
"net/http"
"os"
"path"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
var (
s3_endpoint string
s3_region = "us"
s3_bucket string
s3_access_key string
s3_secret_key string
s3_path_style bool
)
func init() {
existing_backends = append(existing_backends, "s3")
if endpoint, ok := os.LookupEnv("S3_ENDPOINT"); ok {
backend = "s3"
s3_endpoint = endpoint
}
if region, ok := os.LookupEnv("S3_REGION"); ok {
backend = "s3"
s3_region = region
}
s3_bucket, _ = os.LookupEnv("S3_BUCKET")
s3_access_key, _ = os.LookupEnv("S3_ACCESS_KEY")
s3_secret_key, _ = os.LookupEnv("S3_SECRET_KEY")
if path_style, ok := os.LookupEnv("S3_PATH_STYLE"); ok {
s3_path_style = path_style == "1" || path_style == "ON" || path_style == "on" || path_style == "TRUE" || path_style == "true" || path_style == "yes" || path_style == "YES"
}
flag.StringVar(&s3_endpoint, "s3-endpoint", s3_endpoint, "When using S3 backend, endpoint to use")
flag.StringVar(&s3_region, "s3-region", s3_region, "When using S3 backend, region to use")
flag.StringVar(&s3_bucket, "s3-bucket", s3_bucket, "When using S3 backend, bucket to use")
flag.StringVar(&s3_access_key, "s3-access-key", s3_access_key, "When using S3 backend, Access Key")
flag.StringVar(&s3_secret_key, "s3-secret-key", s3_secret_key, "When using S3 backend, Secret Key")
flag.BoolVar(&s3_path_style, "s3-path-style", s3_path_style, "When using S3 backend, force path style (when using minio)")
}
type S3FileBackend struct {
Endpoint string
Region string
Bucket string
AccessKey string
SecretKey string
PathStyle bool
BaseDir string
}
func (l *S3FileBackend) getPath(box, name string) string {
if l.BaseDir == "" {
return path.Join(box, name+".jpg")
} else {
return path.Join(l.BaseDir, box, name+".jpg")
}
}
func (l *S3FileBackend) newSession() (*session.Session, error) {
return session.NewSession(&aws.Config{
Credentials: credentials.NewStaticCredentials(l.AccessKey, l.SecretKey, ""),
Endpoint: &l.Endpoint,
Region: &l.Region,
S3ForcePathStyle: &l.PathStyle,
})
}
func (l *S3FileBackend) DeletePicture(box, name string) error {
s, err := l.newSession()
if err != nil {
return err
}
log.Println(l.getPath(box, name))
input := &s3.DeleteObjectsInput{
Bucket: aws.String(l.Bucket),
Delete: &s3.Delete{
Objects: []*s3.ObjectIdentifier{
{
Key: aws.String(l.getPath(box, name)),
},
},
},
}
_, err = s3.New(s).DeleteObjects(input)
return err
}
func (l *S3FileBackend) ServeFile() http.Handler {
return l
}
func (l *S3FileBackend) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s, err := l.newSession()
if err != nil {
return
}
result, err := s3.New(s).GetObject(&s3.GetObjectInput{
Bucket: aws.String(l.Bucket),
Key: aws.String(r.URL.Path),
})
if err != nil {
return
}
io.Copy(w, result.Body)
}
func (l *S3FileBackend) createPictureInfo(local_path, name string, file *s3.Object) *Picture {
return &Picture{
local_path, // Path
name, // Basename
name[:len(name)-len(path.Ext(name))], // Sanitized filename
*file.LastModified, // UploadTime
}
}
func (l *S3FileBackend) GetPicture(box, name string, w io.Writer) error {
s, err := l.newSession()
if err != nil {
return err
}
s3_path := l.getPath(box, name)
input := &s3.GetObjectInput{
Bucket: aws.String(l.Bucket),
Key: aws.String(s3_path),
}
result, err := s3.New(s).GetObject(input)
if err != nil {
return err
}
_, err = io.Copy(w, result.Body)
return err
}
func (l *S3FileBackend) GetPictureInfo(box, name string) (*Picture, error) {
s, err := l.newSession()
if err != nil {
return nil, err
}
input := &s3.ListObjectsInput{
Bucket: aws.String(l.Bucket),
Prefix: aws.String(l.getPath(box, name)),
}
result, err := s3.New(s).ListObjects(input)
if err != nil {
return nil, err
}
var pictures []*Picture
for _, file := range result.Contents {
pictures = append(pictures, l.createPictureInfo(*file.Key, path.Base(*file.Key), file))
break
}
if len(pictures) == 0 {
return nil, fmt.Errorf("Object not found")
}
return pictures[0], nil
}
func (l *S3FileBackend) ListPictures(box string) ([]*Picture, error) {
s, err := l.newSession()
if err != nil {
return nil, err
}
input := &s3.ListObjectsInput{
Bucket: aws.String(l.Bucket),
}
if l.BaseDir == "" {
input.Prefix = aws.String(box)
} else {
input.Prefix = aws.String(path.Join(l.BaseDir, box))
}
result, err := s3.New(s).ListObjects(input)
if err != nil {
return nil, err
}
pictures := make([]*Picture, 0)
for _, file := range result.Contents {
pictures = append(pictures, l.createPictureInfo(path.Join(box, *file.Key), path.Base(*file.Key), file))
}
return pictures, nil
}
func (l *S3FileBackend) MovePicture(box_from, box_to, name string) error {
s, err := l.newSession()
if err != nil {
return err
}
log.Println(l.getPath(box_from, name), l.getPath(box_to, name))
_, err = s3.New(s).CopyObject(&s3.CopyObjectInput{
Bucket: aws.String(l.Bucket),
CopySource: aws.String(path.Join("", l.Bucket, l.getPath(box_from, name))),
Key: aws.String(l.getPath(box_to, name)),
})
if err != nil {
return err
}
log.Println(l.getPath(box_from, name))
input := &s3.DeleteObjectsInput{
Bucket: aws.String(l.Bucket),
Delete: &s3.Delete{
Objects: []*s3.ObjectIdentifier{
{
Key: aws.String(l.getPath(box_from, name)),
},
},
},
}
_, err = s3.New(s).DeleteObjects(input)
return err
}
func (l *S3FileBackend) PutPicture(box, name string, img *image.Image) error {
s, err := l.newSession()
if err != nil {
return err
}
bbuf := new(bytes.Buffer)
err = jpeg.Encode(bbuf, *img, nil)
if err != nil {
return err
}
_, err = s3manager.NewUploader(s).Upload(&s3manager.UploadInput{
Bucket: aws.String(l.Bucket),
ACL: aws.String("public-read"),
Key: aws.String(l.getPath(box, name)),
Body: bbuf,
})
return err
}

9
go.mod Normal file
View file

@ -0,0 +1,9 @@
module git.nemunai.re/youp0m
go 1.16
require (
github.com/aws/aws-sdk-go v1.44.136
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b
)

108
go.sum Normal file
View file

@ -0,0 +1,108 @@
github.com/aws/aws-sdk-go v1.44.91 h1:SRWmuX7PTyhBdLuvSfM7KWrWISJsrRsUPcFDSFduRxY=
github.com/aws/aws-sdk-go v1.44.91/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.95 h1:QwmA+PeR6v4pF0f/dPHVPWGAshAhb9TnGZBTM5uKuI8=
github.com/aws/aws-sdk-go v1.44.95/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.96 h1:S9paaqnJ0AJ95t5AB+iK8RM6YNZN0W0Lek1gOVJsEr8=
github.com/aws/aws-sdk-go v1.44.96/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.97 h1:lxgxp7d6uuGsP7jHKIX3GHd7ExFigCIF04VuKf8XUII=
github.com/aws/aws-sdk-go v1.44.97/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.98 h1:fX+NxebSdO/9T6DTNOLhpC+Vv6RNkKRfsMg0a7o/yBo=
github.com/aws/aws-sdk-go v1.44.98/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.99 h1:ITZ9q/fmH+Ksaz2TbyMU2d19vOOWs/hAlt8NbXAieHw=
github.com/aws/aws-sdk-go v1.44.99/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.100 h1:7I86bWNQB+HGDT5z/dJy61J7qgbgLoZ7O51C9eL6hrA=
github.com/aws/aws-sdk-go v1.44.100/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.101 h1:O/em5aIxKI/FkwcWAFKEY+JhPDCRsqoVUC6xEF4tGic=
github.com/aws/aws-sdk-go v1.44.101/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.102 h1:6tUCTGL2UDbFZae1TLGk8vTgeXuzkb8KbAe2FiAeKHc=
github.com/aws/aws-sdk-go v1.44.102/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.103 h1:tbhBHKgiZSIUkG8FcHy3wYKpPVvp65Wn7ZiX0B8phpY=
github.com/aws/aws-sdk-go v1.44.103/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.104 h1:NiPYL60aOSH0TsAzQngx/aBdxC12TXhgw07DQFh76GU=
github.com/aws/aws-sdk-go v1.44.104/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.105 h1:UUwoD1PRKIj3ltrDUYTDQj5fOTK3XsnqolLpRTMmSEM=
github.com/aws/aws-sdk-go v1.44.105/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.109 h1:+Na5JPeS0kiEHoBp5Umcuuf+IDqXqD0lXnM920E31YI=
github.com/aws/aws-sdk-go v1.44.109/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.115 h1:qFYIx97cT3k54Bn/lfM6idHbqRHILJyG0SY/0qlKiG0=
github.com/aws/aws-sdk-go v1.44.115/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.116 h1:NpLIhcvLWXJZAEwvPj3TDHeqp7DleK6ZUVYyW01WNHY=
github.com/aws/aws-sdk-go v1.44.116/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.117 h1:mZuODB3Y4soG9QWAXyGb2po+6Easa/enifpj4MnZ91s=
github.com/aws/aws-sdk-go v1.44.117/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.118 h1:FJOqIRTukf7+Ulp047/k7JB6eqMXNnj7eb+coORThHQ=
github.com/aws/aws-sdk-go v1.44.118/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.119 h1:TPkpDsanBMcZaF5wHwpKhjkapRV/b7d2qdC+a+IPbmY=
github.com/aws/aws-sdk-go v1.44.119/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.120 h1:dsOxGf17H9hCVCA4aWpFWEcJMHkX+Uw7l4pGcxb27wM=
github.com/aws/aws-sdk-go v1.44.120/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.121 h1:ahBRUqUp4qLyGmSM5KKn+TVpZkRmtuLxTWw+6Hq/ebs=
github.com/aws/aws-sdk-go v1.44.121/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo=
github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.123 h1:+vVGJ7+vQU6/wRcgRwSBBrIuG/lLL/0LB3HlN5jFv3c=
github.com/aws/aws-sdk-go v1.44.123/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.124 h1:Xe1WQRUUekZf6ZFm3SD0vplB/AP/hymVqMiRS9LQRIs=
github.com/aws/aws-sdk-go v1.44.124/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.125 h1:yIyCs6HX1BOj6SFTirvBwVM1tTfplKrJOyilIZPtKV8=
github.com/aws/aws-sdk-go v1.44.125/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.126 h1:7HQJw2DNiwpxqMe2H7odGNT2rhO4SRrUe5/8dYXl0Jk=
github.com/aws/aws-sdk-go v1.44.126/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.127 h1:IoO2VfuIQg1aMXnl8l6OpNUKT4Qq5CnJMOyIWoTYXj0=
github.com/aws/aws-sdk-go v1.44.127/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.128 h1:X34pX5t0LIZXjBY11yf9JKMP3c1aZgirh+5PjtaZyJ4=
github.com/aws/aws-sdk-go v1.44.128/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.129 h1:yld8Rc8OCahLtenY1mnve4w1jVeBu/rSiscGzodaDOs=
github.com/aws/aws-sdk-go v1.44.129/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.130 h1:a/qwOxmYJF47xTZvTjECSJXnfRbjegb3YxvCXfETtnY=
github.com/aws/aws-sdk-go v1.44.130/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.132 h1:+IjL9VoR0OXScQ5gyme9xjcolwUkd3uaH144f4Ao+4s=
github.com/aws/aws-sdk-go v1.44.132/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.133 h1:+pWxt9nyKc0jf33rORBaQ93KPjYpmIIy3ozVXdJ82Oo=
github.com/aws/aws-sdk-go v1.44.133/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.135 h1:DJJP/CkEpgafA5p5jlY9VzDRyKrfABVixzIxrK/3tWU=
github.com/aws/aws-sdk-go v1.44.135/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.136 h1:J1KJJssa8pjU8jETYUxwRS37KTcxjACfKd9GK8t+5ZU=
github.com/aws/aws-sdk-go v1.44.136/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs=
gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -6,14 +6,14 @@ import (
"strings"
)
type imagesHandler struct{
type imagesHandler struct {
name string
auth func(*http.Request) (bool)
auth func(*http.Request) bool
hndlr http.Handler
getter func(fname string) (Picture, error)
getter func(fname string) (*Picture, error)
}
func ImagesHandler(name string, auth func(*http.Request) (bool), hndlr http.Handler, getter func(fname string) (Picture, error)) (imagesHandler) {
func ImagesHandler(name string, auth func(*http.Request) bool, hndlr http.Handler, getter func(fname string) (*Picture, error)) imagesHandler {
return imagesHandler{name, auth, hndlr, getter}
}
@ -39,7 +39,7 @@ func (i imagesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
// Check rights
if ! i.auth(r) {
if !i.auth(r) {
w.Header().Set("WWW-Authenticate", "Basic realm=\"YouP0m\"")
http.Error(w, "You are not allowed to perform this request.", http.StatusUnauthorized)
return
@ -50,7 +50,7 @@ func (i imagesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusNotFound)
return
} else {
r.URL.Path = "/" + pict.basename
r.URL.Path = "/" + pict.path
i.hndlr.ServeHTTP(w, r)
}
}

106
main.go
View file

@ -2,27 +2,31 @@ package main
import (
"flag"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"path"
)
var PublishedImgDir string
var NextImgDir string
var ThumbsDir string
var mux = http.NewServeMux()
var (
ThumbsDir string
backend = "local"
)
func main() {
bind := flag.String("bind", ":8080", "Bind port/socket")
htpasswd_file := flag.String("htpasswd", "", "Admin passwords file, Apache htpasswd format")
flag.StringVar(&PublishedImgDir, "publishedimgdir", "./images/published/", "Directory where save published pictures")
flag.StringVar(&NextImgDir, "nextimgdir", "./images/next/", "Directory where save pictures to review")
publishedImgDir := flag.String("publishedimgdir", "published/", "Directory where save published pictures")
nextImgDir := flag.String("nextimgdir", "next/", "Directory where save pictures to review")
baseDir := flag.String("basedir", "images/", "Local base directory where find published and next dirs")
flag.StringVar(&backend, "storage-backend", backend, fmt.Sprintf("Storage backend to use (between: %s)", existing_backends))
flag.StringVar(&ThumbsDir, "thumbsdir", "./images/thumbs/", "Directory where generate thumbs")
flag.Parse()
htpasswd := &Htpasswd{
entries: map[string]string{"admin": "2fClb0C8dIphk"},
}
htpasswd := &Htpasswd{}
if htpasswd_file != nil && *htpasswd_file != "" {
log.Println("Reading htpasswd file...")
@ -30,21 +34,12 @@ func main() {
if htpasswd, err = NewHtpasswd(*htpasswd_file); htpasswd == nil {
log.Fatal("Unable to parse htpasswd:", err)
}
} else {
log.Println("Using default credentials for administrative part: admin:admin")
} else if *nextImgDir == "next/" {
log.Println("Disable admin interface, images will be published without moderation")
nextImgDir = publishedImgDir
}
log.Println("Checking paths...")
if _, err := os.Stat(PublishedImgDir); os.IsNotExist(err) {
if err := os.MkdirAll(PublishedImgDir, 0755); err != nil {
log.Fatal(err)
}
}
if _, err := os.Stat(NextImgDir); os.IsNotExist(err) {
if err := os.MkdirAll(NextImgDir, 0755); err != nil {
log.Fatal(err)
}
}
if _, err := os.Stat(ThumbsDir); os.IsNotExist(err) {
if err := os.MkdirAll(ThumbsDir, 0755); err != nil {
log.Fatal(err)
@ -52,47 +47,69 @@ func main() {
}
log.Println("Registering handlers...")
authFunc := func (r *http.Request) (*User){ return Authenticate(*htpasswd, r) }
authFunc := func(r *http.Request) *User { return Authenticate(*htpasswd, r) }
staticDir, _ := filepath.Abs("static")
if _, err := os.Stat(staticDir); os.IsNotExist(err) {
staticDir, _ = filepath.Abs(filepath.Join(filepath.Dir(os.Args[0]), "static"))
if _, err := os.Stat(staticDir); os.IsNotExist(err) {
log.Fatal(err)
}
if err := sanitizeStaticOptions(); err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, filepath.Join(staticDir, "index.html")) })
mux.Handle("/css/", http.FileServer(http.Dir(staticDir)))
mux.Handle("/js/", http.FileServer(http.Dir(staticDir)))
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = "/"
http.FileServer(Assets).ServeHTTP(w, r)
})
mux.Handle("/api/", http.StripPrefix("/api", ApiHandler(authFunc)))
var storage_backend FileBackend
if backend == "local" {
storage_backend = &LocalFileBackend{*baseDir}
if _, err := os.Stat(path.Join(*baseDir, *publishedImgDir)); os.IsNotExist(err) {
if err := os.MkdirAll(path.Join(*baseDir, *publishedImgDir), 0755); err != nil {
log.Fatal(err)
}
}
if _, err := os.Stat(path.Join(*baseDir, *nextImgDir)); os.IsNotExist(err) {
if err := os.MkdirAll(path.Join(*baseDir, *nextImgDir), 0755); err != nil {
log.Fatal(err)
}
}
} else if backend == "s3" {
storage_backend = &S3FileBackend{s3_endpoint, s3_region, s3_bucket, s3_access_key, s3_secret_key, s3_path_style, *baseDir}
} else {
log.Fatalf("%q is not a valid storage backend.", backend)
}
log.Printf("Using %s storage backend", backend)
pe := &PictureExplorer{
FileBackend: storage_backend,
PublishedImgDir: *publishedImgDir,
NextImgDir: *nextImgDir,
}
mux.Handle("/api/", http.StripPrefix("/api", NewAPIHandler(pe, authFunc)))
mux.Handle("/images/", http.StripPrefix("/images", ImagesHandler(
"images",
func (*http.Request) (bool){ return true; },
http.FileServer(http.Dir(PublishedImgDir)),
GetPublishedImage,
func(*http.Request) bool { return true },
pe.ServeFile(),
pe.GetPublishedImage,
)))
mux.Handle("/images/next/", http.StripPrefix("/images/next", ImagesHandler(
"next",
func (r *http.Request) (bool){ return authFunc(r) != nil; },
http.FileServer(http.Dir(NextImgDir)),
GetNextImage,
func(r *http.Request) bool { return authFunc(r) != nil },
pe.ServeFile(),
pe.GetNextImage,
)))
mux.Handle("/images/thumbs/", http.StripPrefix("/images/thumbs", ImagesHandler(
"thumbs",
func (*http.Request) (bool){ return true; },
func(*http.Request) bool { return true },
http.FileServer(http.Dir(ThumbsDir)),
GetPublishedImage,
pe.GetPublishedImage,
)))
mux.Handle("/images/next/thumbs/", http.StripPrefix("/images/next/thumbs", ImagesHandler(
"nexthumbs",
func (r *http.Request) (bool){ return authFunc(r) != nil; },
func(r *http.Request) bool { return authFunc(r) != nil },
http.FileServer(http.Dir(ThumbsDir)),
GetNextImage,
pe.GetNextImage,
)))
mux.HandleFunc("/admin/", func(w http.ResponseWriter, r *http.Request) {
@ -100,7 +117,8 @@ func main() {
w.Header().Set("WWW-Authenticate", "Basic realm=\"YouP0m\"")
http.Error(w, "You are not allowed to perform this request.", http.StatusUnauthorized)
} else {
http.ServeFile(w, r, filepath.Join(staticDir, "admin.html"))
r.URL.Path = "/admin.html"
http.FileServer(Assets).ServeHTTP(w, r)
}
})

View file

@ -1,22 +1,27 @@
package main
import (
"encoding/base64"
"errors"
"io"
"io/ioutil"
"image"
_ "image/gif"
"image/jpeg"
_ "image/png"
"io"
"os"
"path/filepath"
"path"
"sort"
"strconv"
"time"
"github.com/nfnt/resize"
)
type PictureExplorer struct {
FileBackend
PublishedImgDir string
NextImgDir string
}
type Picture struct {
path string
basename string
@ -24,143 +29,92 @@ type Picture struct {
UploadTime time.Time `json:"upload_time"`
}
type ByUploadTime []Picture
type ByUploadTime []*Picture
func (a ByUploadTime) Len() int { return len(a) }
func (a ByUploadTime) Len() int { return len(a) }
func (a ByUploadTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByUploadTime) Less(i, j int) bool { return a[i].UploadTime.Sub(a[j].UploadTime).Nanoseconds() < 0 }
func (a ByUploadTime) Less(i, j int) bool {
return a[i].UploadTime.Sub(a[j].UploadTime).Nanoseconds() < 0
}
func getImages(dir string) ([]Picture, error) {
if files, err := ioutil.ReadDir(dir); err != nil {
func (e *PictureExplorer) GetNextImages() (pictures []*Picture, err error) {
pictures, err = e.ListPictures(e.NextImgDir)
sort.Sort(ByUploadTime(pictures))
return
}
func (e *PictureExplorer) GetPublishedImages() (pictures []*Picture, err error) {
pictures, err = e.ListPictures(e.PublishedImgDir)
sort.Sort(ByUploadTime(pictures))
return
}
func (e *PictureExplorer) GetLastImage() (*Picture, error) {
picts, err := e.GetPublishedImages()
if err != nil {
return nil, err
} else {
pictures := make([]Picture, 0)
for _, file := range files {
if !file.IsDir() {
filename := file.Name()
pictures = append(pictures, Picture{
filepath.Join(dir, filename), // Path
filename, // Basename
filename[:len(filename) - len(filepath.Ext(filename))], // Sanitized filename
file.ModTime(), // UploadTime
})
}
}
sort.Sort(ByUploadTime(pictures))
return pictures, nil
}
return picts[len(picts)-1], nil
}
func GetNextImages() ([]Picture, error) {
return getImages(NextImgDir)
func (e *PictureExplorer) GetPublishedImage(fname string) (*Picture, error) {
return e.GetPictureInfo(e.PublishedImgDir, fname)
}
func GetPublishedImages() ([]Picture, error) {
return getImages(PublishedImgDir)
func (e *PictureExplorer) GetNextImage(fname string) (*Picture, error) {
return e.GetPictureInfo(e.NextImgDir, fname)
}
func GetLastImage() (Picture, error) {
if picts, err := GetPublishedImages(); err != nil {
return Picture{}, err
} else {
return picts[len(picts)-1], nil
}
}
func getImage(flist func() ([]Picture, error), fname string) (Picture, error) {
if picts, err := flist(); err != nil {
return Picture{}, err
} else if pid, err := strconv.Atoi(fname); err == nil {
if pid < len(picts) {
return picts[pid], nil
} else {
return Picture{}, errors.New("Invalid picture identifier")
}
} else {
for _, pict := range picts {
if pict.Name == fname || pict.basename == fname {
return pict, nil
}
}
return Picture{}, errors.New("No such picture")
}
}
func GetPublishedImage(fname string) (Picture, error) {
return getImage(GetPublishedImages, fname)
}
func GetNextImage(fname string) (Picture, error) {
return getImage(GetNextImages, fname)
}
func UniqueImage(filename string) (bool) {
if pict, _ := GetPublishedImage(filename); pict.path != "" {
func (e *PictureExplorer) IsUniqueName(filename string) bool {
if pict, _ := e.GetPublishedImage(filename); pict != nil {
return false
} else {
if pict, _ := GetNextImage(filename); pict.path != "" {
return false
} else {
return true
}
}
if pict, _ := e.GetNextImage(filename); pict != nil {
return false
}
return true
}
func AddImage(filename string, blob io.ReadCloser) (error) {
func (e *PictureExplorer) AddImage(filename string, blob io.ReadCloser) error {
// Check the name is not already used
if ok := UniqueImage(filename); !ok {
if ok := e.IsUniqueName(filename); !ok {
return errors.New("This filename is already used, please choose another one.")
}
// Convert to JPEG
} else if img, _, err := image.Decode(blob); err != nil {
img, _, err := image.Decode(base64.NewDecoder(base64.StdEncoding, blob))
if err != nil {
return err
}
// Save file
} else if fw, err := os.Create(filepath.Join(NextImgDir, filename + ".jpg")); err != nil {
if err := e.PutPicture(e.NextImgDir, filename, &img); err != nil {
return err
} else if err := jpeg.Encode(fw, img, nil); err != nil {
return err
} else {
fw.Close()
thumb := resize.Thumbnail(300, 185, img, resize.Lanczos3)
// Save thumbnail
if fw, err := os.Create(filepath.Join(ThumbsDir, filename + ".jpg")); err != nil {
return err
} else if err := jpeg.Encode(fw, thumb, nil); err != nil {
return err
} else {
fw.Close()
}
}
return nil
thumb := resize.Thumbnail(300, 185, img, resize.Lanczos3)
// Save thumbnail
fw, err := os.Create(path.Join(ThumbsDir, filename+".jpg"))
if err != nil {
return err
}
defer fw.Close()
return jpeg.Encode(fw, thumb, nil)
}
func (p Picture) Publish() (error) {
npath := filepath.Join(PublishedImgDir, p.basename)
if err := os.Rename(p.path, npath); err != nil {
return err
} else if err := os.Chtimes(npath, time.Now(), time.Now()); err != nil {
return err
} else {
return nil
}
func (e *PictureExplorer) Publish(p *Picture) error {
return e.MovePicture(e.NextImgDir, e.PublishedImgDir, p.Name)
}
func (p Picture) Unpublish() (error) {
if err := os.Rename(p.path, filepath.Join(NextImgDir, p.basename)); err != nil {
return err
} else {
return nil
}
func (e *PictureExplorer) Unpublish(p *Picture) error {
return e.MovePicture(e.PublishedImgDir, e.NextImgDir, p.Name)
}
func (p Picture) Remove() (error) {
if err := os.Remove(p.path); err != nil {
return err
} else {
return nil
}
func (e *PictureExplorer) Remove(p *Picture) error {
return e.DeletePicture(path.Dir(p.path), p.Name)
}

10
renovate.json Normal file
View file

@ -0,0 +1,10 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"packageRules": [
{
"matchPackageNames": ["github.com/aws/aws-sdk-go"],
"automerge": true,
"automergeType": "branch"
}
]
}

14
static.go Normal file
View file

@ -0,0 +1,14 @@
package main
import (
"net/http"
)
func init() {
mux.HandleFunc("/css/", func(w http.ResponseWriter, r *http.Request) {
http.FileServer(Assets).ServeHTTP(w, r)
})
mux.HandleFunc("/js/", func(w http.ResponseWriter, r *http.Request) {
http.FileServer(Assets).ServeHTTP(w, r)
})
}

View file

@ -4,9 +4,6 @@
<title>YouP0m Administration</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" media="all" href="/css/style.css">
<script type="text/javascript" src="/js/prototype.js"></script>
<script type="text/javascript" src="/js/youp0m.js"></script>
<script type="text/javascript" src="/js/youp0m-admin.js"></script>
</head>
<body>
<h1>Welcome on YouP0m!</h1>
@ -19,5 +16,7 @@
<script type="text/javascript">
document.write("<h2>We are loading the newest cute pictures for you, please wait&hellip;</h2>");
</script>
<script type="text/javascript" src="/js/youp0m.js"></script>
<script type="text/javascript" src="/js/youp0m-admin.js"></script>
</body>
</html>

View file

@ -28,6 +28,24 @@ figure.moz {
vertical-align: middle;
}
figure.moz .menu {
position: absolute;
margin: inherit;
display: none;
}
figure.moz:hover .menu {
display: block;
}
figure.moz:hover .menu a {
background: rgba(0,0,0,0.5);
color: #eee;
padding: 6px 10px 8px 10px;
text-decoration: none;
}
figure.moz .menu a:hover {
background: rgba(0,0,0,0.8);
}
.big img {
max-height: calc(100vh - 5px);
max-width: calc(100vw - 5px);

View file

@ -4,8 +4,6 @@
<title>YouP0m</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" media="all" href="/css/style.css">
<script type="text/javascript" src="/js/prototype.js"></script>
<script type="text/javascript" src="/js/youp0m.js"></script>
</head>
<body>
<h1>Welcome on YouP0m!</h1>
@ -18,5 +16,6 @@
<script type="text/javascript">
document.write("<h2>We are loading a cute picture just for you, please wait&hellip;</h2>");
</script>
<script type="text/javascript" src="/js/youp0m.js"></script>
</body>
</html>

7588
static/js/prototype.js vendored

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,47 @@
api_url = "/api/next/";
img_url = "/images/next/";
function admin_ctrl(figure) {
var menu = document.createElement("ul");
var item = document.createElement("li", "test");
menu.appendChild(item);
figure.appendChild(menu)
function publish_pict(pict) {
fetch(api_url + "/" + pict.name + "/publish").then(response => {
picts = null;
sync()
});
}
function delete_pict(pict) {
fetch(api_url + "/" + pict.name, { method: "DELETE" }).then(response => {
picts = null;
sync()
});
}
function admin_ctrl(pict) {
var figure = document.createElement("figure");
figure.className = "moz";
var img = document.createElement("img");
img.src = img_url + pict.name;
img.alt = pict.name;
var div = document.createElement("div");
div.className = "menu"
var linkPub = document.createElement("a");
linkPub.onclick = function(e) {
publish_pict(pict);
return false;
};
linkPub.href = "#";
linkPub.innerHTML = "Publier";
var linkDel = document.createElement("a");
linkDel.onclick = function(e) {
delete_pict(pict);
return false;
};
linkDel.href = "#";
linkDel.innerHTML = "Supprimer";
div.appendChild(linkPub);
div.appendChild(linkDel);
figure.appendChild(div);
figure.appendChild(img);
document.body.appendChild(figure);
}
function sync() {

View file

@ -3,41 +3,54 @@ var api_url = "/api/images";
var img_url = "/images/";
function get_picts(then, then_value) {
new Ajax.Request(api_url, {
method: 'get',
onSuccess: function(transport) {
picts = transport.responseText.evalJSON();
if (then) then(then_value);
},
fetch(api_url)
.then(function(response) {
return response.json();
})
.then(function(my_picts) {
picts = my_picts;
if (then) then(then_value);
})
.catch(function(error) {
display_error(error);
});
}
function show_mozaic() {
function show_mozaic(callpict) {
if (!picts) {
get_picts(show_mozaic);
get_picts(show_mozaic, callpict);
return;
}
if (!picts.length) {
document.body.innerHTML = '<h1>Welcome on YouP0m!</h1><h2>There is no image currently, come back soon!</h2>';
return;
}
if (!callpict) {
callpict = function (pict) {
var figure = document.createElement("figure");
figure.className = "moz";
var img = document.createElement("img");
img.src = img_url + pict.name;
img.alt = pict.name;
var link = document.createElement("a");
link.onclick = function(e) {
window.history.pushState(null, "YouP0m", link.href);
sync();
return false;
};
link.href = "/" + pict.name;
link.appendChild(img);
figure.appendChild(link);
document.body.appendChild(figure);
}
}
while (document.body.hasChildNodes())
document.body.removeChild(document.body.lastChild);
picts.forEach(function (pict) {
var figure = document.createElement("figure");
figure.className = "moz";
var img = document.createElement("img");
img.src = img_url + pict.name;
img.alt = pict.name;
var link = document.createElement("a");
link.onclick = function(e) {
window.history.pushState(null, "YouP0m", link.href);
sync();
return false;
};
link.href = "/" + pict.name;
link.appendChild(img);
figure.appendChild(link);
document.body.appendChild(figure);
});
picts.forEach(callpict);
}
function show_picture(id) {
@ -46,6 +59,11 @@ function show_picture(id) {
return;
}
if (picts.length == 0) {
document.body.innerHTML = '<h1>Welcome on YouP0m!</h1><h2>There is no image currently, come back soon!</h2>';
return
}
if (id == null || id == "")
id = picts[Math.floor(Math.random() * picts.length)].name;
@ -69,16 +87,16 @@ function show_picture(id) {
function get_picture(id) {
if (id == null) id = "last";
new Ajax.Request(api_url + "/" + id, {
method: 'get',
onSuccess: function(transport) {
var response = transport.responseText.evalJSON();
display_picture(response);
},
onFailure: function(transport) {
var response = transport.responseText.evalJSON();
display_error(response["errmsg"]);
}
fetch(api_url + "/" + id)
.then(function(response) {
return response.json();
})
.then(function(my_pict) {
display_picture(my_pict);
})
.catch(function(error) {
display_error(error);
});
}
@ -108,7 +126,7 @@ function display_picture(pict, next) {
}
function display_error(msg) {
$$("body")[0].innerHTML = '<h1>An error occurs</h1><h2>' + msg + '</h2>';
document.body.innerHTML = '<h1>An error occurs</h1><h2>' + msg + '</h2>';
}
function sync() {

View file

@ -1,7 +0,0 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- tip

View file

@ -1,13 +0,0 @@
Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

View file

@ -1,149 +0,0 @@
Resize
======
Image resizing for the [Go programming language](http://golang.org) with common interpolation methods.
[![Build Status](https://travis-ci.org/nfnt/resize.svg)](https://travis-ci.org/nfnt/resize)
Installation
------------
```bash
$ go get github.com/nfnt/resize
```
It's that easy!
Usage
-----
This package needs at least Go 1.1. Import package with
```go
import "github.com/nfnt/resize"
```
The resize package provides 2 functions:
* `resize.Resize` creates a scaled image with new dimensions (`width`, `height`) using the interpolation function `interp`.
If either `width` or `height` is set to 0, it will be set to an aspect ratio preserving value.
* `resize.Thumbnail` downscales an image preserving its aspect ratio to the maximum dimensions (`maxWidth`, `maxHeight`).
It will return the original image if original sizes are smaller than the provided dimensions.
```go
resize.Resize(width, height uint, img image.Image, interp resize.InterpolationFunction) image.Image
resize.Thumbnail(maxWidth, maxHeight uint, img image.Image, interp resize.InterpolationFunction) image.Image
```
The provided interpolation functions are (from fast to slow execution time)
- `NearestNeighbor`: [Nearest-neighbor interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation)
- `Bilinear`: [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation)
- `Bicubic`: [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation)
- `MitchellNetravali`: [Mitchell-Netravali interpolation](http://dl.acm.org/citation.cfm?id=378514)
- `Lanczos2`: [Lanczos resampling](http://en.wikipedia.org/wiki/Lanczos_resampling) with a=2
- `Lanczos3`: [Lanczos resampling](http://en.wikipedia.org/wiki/Lanczos_resampling) with a=3
Which of these methods gives the best results depends on your use case.
Sample usage:
```go
package main
import (
"github.com/nfnt/resize"
"image/jpeg"
"log"
"os"
)
func main() {
// open "test.jpg"
file, err := os.Open("test.jpg")
if err != nil {
log.Fatal(err)
}
// decode jpeg into image.Image
img, err := jpeg.Decode(file)
if err != nil {
log.Fatal(err)
}
file.Close()
// resize to width 1000 using Lanczos resampling
// and preserve aspect ratio
m := resize.Resize(1000, 0, img, resize.Lanczos3)
out, err := os.Create("test_resized.jpg")
if err != nil {
log.Fatal(err)
}
defer out.Close()
// write new image to file
jpeg.Encode(out, m, nil)
}
```
Caveats
-------
* Optimized access routines are used for `image.RGBA`, `image.NRGBA`, `image.RGBA64`, `image.NRGBA64`, `image.YCbCr`, `image.Gray`, and `image.Gray16` types. All other image types are accessed in a generic way that will result in slow processing speed.
* JPEG images are stored in `image.YCbCr`. This image format stores data in a way that will decrease processing speed. A resize may be up to 2 times slower than with `image.RGBA`.
Downsizing Samples
-------
Downsizing is not as simple as it might look like. Images have to be filtered before they are scaled down, otherwise aliasing might occur.
Filtering is highly subjective: Applying too much will blur the whole image, too little will make aliasing become apparent.
Resize tries to provide sane defaults that should suffice in most cases.
### Artificial sample
Original image
![Rings](http://nfnt.github.com/img/rings_lg_orig.png)
<table>
<tr>
<th><img src="http://nfnt.github.com/img/rings_300_NearestNeighbor.png" /><br>Nearest-Neighbor</th>
<th><img src="http://nfnt.github.com/img/rings_300_Bilinear.png" /><br>Bilinear</th>
</tr>
<tr>
<th><img src="http://nfnt.github.com/img/rings_300_Bicubic.png" /><br>Bicubic</th>
<th><img src="http://nfnt.github.com/img/rings_300_MitchellNetravali.png" /><br>Mitchell-Netravali</th>
</tr>
<tr>
<th><img src="http://nfnt.github.com/img/rings_300_Lanczos2.png" /><br>Lanczos2</th>
<th><img src="http://nfnt.github.com/img/rings_300_Lanczos3.png" /><br>Lanczos3</th>
</tr>
</table>
### Real-Life sample
Original image
![Original](http://nfnt.github.com/img/IMG_3694_720.jpg)
<table>
<tr>
<th><img src="http://nfnt.github.com/img/IMG_3694_300_NearestNeighbor.png" /><br>Nearest-Neighbor</th>
<th><img src="http://nfnt.github.com/img/IMG_3694_300_Bilinear.png" /><br>Bilinear</th>
</tr>
<tr>
<th><img src="http://nfnt.github.com/img/IMG_3694_300_Bicubic.png" /><br>Bicubic</th>
<th><img src="http://nfnt.github.com/img/IMG_3694_300_MitchellNetravali.png" /><br>Mitchell-Netravali</th>
</tr>
<tr>
<th><img src="http://nfnt.github.com/img/IMG_3694_300_Lanczos2.png" /><br>Lanczos2</th>
<th><img src="http://nfnt.github.com/img/IMG_3694_300_Lanczos3.png" /><br>Lanczos3</th>
</tr>
</table>
License
-------
Copyright (c) 2012 Jan Schlicht <janschlicht@gmail.com>
Resize is released under a MIT style license.

View file

@ -1,438 +0,0 @@
/*
Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/
package resize
import "image"
// Keep value in [0,255] range.
func clampUint8(in int32) uint8 {
// casting a negative int to an uint will result in an overflown
// large uint. this behavior will be exploited here and in other functions
// to achieve a higher performance.
if uint32(in) < 256 {
return uint8(in)
}
if in > 255 {
return 255
}
return 0
}
// Keep value in [0,65535] range.
func clampUint16(in int64) uint16 {
if uint64(in) < 65536 {
return uint16(in)
}
if in > 65535 {
return 65535
}
return 0
}
func resizeGeneric(in image.Image, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]int64
var sum int64
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case xi < 0:
xi = 0
case xi >= maxX:
xi = maxX
}
r, g, b, a := in.At(xi+in.Bounds().Min.X, x+in.Bounds().Min.Y).RGBA()
rgba[0] += int64(coeff) * int64(r)
rgba[1] += int64(coeff) * int64(g)
rgba[2] += int64(coeff) * int64(b)
rgba[3] += int64(coeff) * int64(a)
sum += int64(coeff)
}
}
offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
value := clampUint16(rgba[0] / sum)
out.Pix[offset+0] = uint8(value >> 8)
out.Pix[offset+1] = uint8(value)
value = clampUint16(rgba[1] / sum)
out.Pix[offset+2] = uint8(value >> 8)
out.Pix[offset+3] = uint8(value)
value = clampUint16(rgba[2] / sum)
out.Pix[offset+4] = uint8(value >> 8)
out.Pix[offset+5] = uint8(value)
value = clampUint16(rgba[3] / sum)
out.Pix[offset+6] = uint8(value >> 8)
out.Pix[offset+7] = uint8(value)
}
}
}
func resizeRGBA(in *image.RGBA, out *image.RGBA, scale float64, coeffs []int16, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]int32
var sum int32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 4
case xi >= maxX:
xi = 4 * maxX
default:
xi = 0
}
rgba[0] += int32(coeff) * int32(row[xi+0])
rgba[1] += int32(coeff) * int32(row[xi+1])
rgba[2] += int32(coeff) * int32(row[xi+2])
rgba[3] += int32(coeff) * int32(row[xi+3])
sum += int32(coeff)
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4
out.Pix[xo+0] = clampUint8(rgba[0] / sum)
out.Pix[xo+1] = clampUint8(rgba[1] / sum)
out.Pix[xo+2] = clampUint8(rgba[2] / sum)
out.Pix[xo+3] = clampUint8(rgba[3] / sum)
}
}
}
func resizeNRGBA(in *image.NRGBA, out *image.RGBA, scale float64, coeffs []int16, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]int32
var sum int32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 4
case xi >= maxX:
xi = 4 * maxX
default:
xi = 0
}
rgba[0] += int32(coeff) * int32(row[xi+0])
rgba[1] += int32(coeff) * int32(row[xi+1])
rgba[2] += int32(coeff) * int32(row[xi+2])
rgba[3] += int32(coeff) * int32(row[xi+3])
sum += int32(coeff)
// Forward alpha-premultiplication
a := int32(row[xi+3])
rgba[0] *= a
rgba[0] /= 0xff
rgba[1] *= a
rgba[1] /= 0xff
rgba[2] *= a
rgba[2] /= 0xff
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4
out.Pix[xo+0] = clampUint8(rgba[0] / sum)
out.Pix[xo+1] = clampUint8(rgba[1] / sum)
out.Pix[xo+2] = clampUint8(rgba[2] / sum)
out.Pix[xo+3] = clampUint8(rgba[3] / sum)
}
}
}
func resizeRGBA64(in *image.RGBA64, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]int64
var sum int64
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 8
case xi >= maxX:
xi = 8 * maxX
default:
xi = 0
}
rgba[0] += int64(coeff) * (int64(row[xi+0])<<8 | int64(row[xi+1]))
rgba[1] += int64(coeff) * (int64(row[xi+2])<<8 | int64(row[xi+3]))
rgba[2] += int64(coeff) * (int64(row[xi+4])<<8 | int64(row[xi+5]))
rgba[3] += int64(coeff) * (int64(row[xi+6])<<8 | int64(row[xi+7]))
sum += int64(coeff)
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
value := clampUint16(rgba[0] / sum)
out.Pix[xo+0] = uint8(value >> 8)
out.Pix[xo+1] = uint8(value)
value = clampUint16(rgba[1] / sum)
out.Pix[xo+2] = uint8(value >> 8)
out.Pix[xo+3] = uint8(value)
value = clampUint16(rgba[2] / sum)
out.Pix[xo+4] = uint8(value >> 8)
out.Pix[xo+5] = uint8(value)
value = clampUint16(rgba[3] / sum)
out.Pix[xo+6] = uint8(value >> 8)
out.Pix[xo+7] = uint8(value)
}
}
}
func resizeNRGBA64(in *image.NRGBA64, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]int64
var sum int64
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 8
case xi >= maxX:
xi = 8 * maxX
default:
xi = 0
}
rgba[0] += int64(coeff) * int64(uint16(row[xi+0])<<8|uint16(row[xi+1]))
rgba[1] += int64(coeff) * int64(uint16(row[xi+2])<<8|uint16(row[xi+3]))
rgba[2] += int64(coeff) * int64(uint16(row[xi+4])<<8|uint16(row[xi+5]))
rgba[3] += int64(coeff) * int64(uint16(row[xi+6])<<8|uint16(row[xi+7]))
sum += int64(coeff)
// Forward alpha-premultiplication
a := int64(uint16(row[xi+6])<<8 | uint16(row[xi+7]))
rgba[0] *= a
rgba[0] /= 0xffff
rgba[1] *= a
rgba[1] /= 0xffff
rgba[2] *= a
rgba[2] /= 0xffff
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
value := clampUint16(rgba[0] / sum)
out.Pix[xo+0] = uint8(value >> 8)
out.Pix[xo+1] = uint8(value)
value = clampUint16(rgba[1] / sum)
out.Pix[xo+2] = uint8(value >> 8)
out.Pix[xo+3] = uint8(value)
value = clampUint16(rgba[2] / sum)
out.Pix[xo+4] = uint8(value >> 8)
out.Pix[xo+5] = uint8(value)
value = clampUint16(rgba[3] / sum)
out.Pix[xo+6] = uint8(value >> 8)
out.Pix[xo+7] = uint8(value)
}
}
}
func resizeGray(in *image.Gray, out *image.Gray, scale float64, coeffs []int16, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[(x-newBounds.Min.X)*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var gray int32
var sum int32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case xi < 0:
xi = 0
case xi >= maxX:
xi = maxX
}
gray += int32(coeff) * int32(row[xi])
sum += int32(coeff)
}
}
offset := (y-newBounds.Min.Y)*out.Stride + (x - newBounds.Min.X)
out.Pix[offset] = clampUint8(gray / sum)
}
}
}
func resizeGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []int32, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var gray int64
var sum int64
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 2
case xi >= maxX:
xi = 2 * maxX
default:
xi = 0
}
gray += int64(coeff) * int64(uint16(row[xi+0])<<8|uint16(row[xi+1]))
sum += int64(coeff)
}
}
offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*2
value := clampUint16(gray / sum)
out.Pix[offset+0] = uint8(value >> 8)
out.Pix[offset+1] = uint8(value)
}
}
}
func resizeYCbCr(in *ycc, out *ycc, scale float64, coeffs []int16, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var p [3]int32
var sum int32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
coeff := coeffs[ci+i]
if coeff != 0 {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 3
case xi >= maxX:
xi = 3 * maxX
default:
xi = 0
}
p[0] += int32(coeff) * int32(row[xi+0])
p[1] += int32(coeff) * int32(row[xi+1])
p[2] += int32(coeff) * int32(row[xi+2])
sum += int32(coeff)
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3
out.Pix[xo+0] = clampUint8(p[0] / sum)
out.Pix[xo+1] = clampUint8(p[1] / sum)
out.Pix[xo+2] = clampUint8(p[2] / sum)
}
}
}
func nearestYCbCr(in *ycc, out *ycc, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var p [3]float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 3
case xi >= maxX:
xi = 3 * maxX
default:
xi = 0
}
p[0] += float32(row[xi+0])
p[1] += float32(row[xi+1])
p[2] += float32(row[xi+2])
sum++
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3
out.Pix[xo+0] = floatToUint8(p[0] / sum)
out.Pix[xo+1] = floatToUint8(p[1] / sum)
out.Pix[xo+2] = floatToUint8(p[2] / sum)
}
}
}

View file

@ -1,43 +0,0 @@
package resize
import (
"testing"
)
func Test_ClampUint8(t *testing.T) {
var testData = []struct {
in int32
expected uint8
}{
{0, 0},
{255, 255},
{128, 128},
{-2, 0},
{256, 255},
}
for _, test := range testData {
actual := clampUint8(test.in)
if actual != test.expected {
t.Fail()
}
}
}
func Test_ClampUint16(t *testing.T) {
var testData = []struct {
in int64
expected uint16
}{
{0, 0},
{65535, 65535},
{128, 128},
{-2, 0},
{65536, 65535},
}
for _, test := range testData {
actual := clampUint16(test.in)
if actual != test.expected {
t.Fail()
}
}
}

View file

@ -1,143 +0,0 @@
/*
Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/
package resize
import (
"math"
)
func nearest(in float64) float64 {
if in >= -0.5 && in < 0.5 {
return 1
}
return 0
}
func linear(in float64) float64 {
in = math.Abs(in)
if in <= 1 {
return 1 - in
}
return 0
}
func cubic(in float64) float64 {
in = math.Abs(in)
if in <= 1 {
return in*in*(1.5*in-2.5) + 1.0
}
if in <= 2 {
return in*(in*(2.5-0.5*in)-4.0) + 2.0
}
return 0
}
func mitchellnetravali(in float64) float64 {
in = math.Abs(in)
if in <= 1 {
return (7.0*in*in*in - 12.0*in*in + 5.33333333333) * 0.16666666666
}
if in <= 2 {
return (-2.33333333333*in*in*in + 12.0*in*in - 20.0*in + 10.6666666667) * 0.16666666666
}
return 0
}
func sinc(x float64) float64 {
x = math.Abs(x) * math.Pi
if x >= 1.220703e-4 {
return math.Sin(x) / x
}
return 1
}
func lanczos2(in float64) float64 {
if in > -2 && in < 2 {
return sinc(in) * sinc(in*0.5)
}
return 0
}
func lanczos3(in float64) float64 {
if in > -3 && in < 3 {
return sinc(in) * sinc(in*0.3333333333333333)
}
return 0
}
// range [-256,256]
func createWeights8(dy, filterLength int, blur, scale float64, kernel func(float64) float64) ([]int16, []int, int) {
filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1))
filterFactor := math.Min(1./(blur*scale), 1)
coeffs := make([]int16, dy*filterLength)
start := make([]int, dy)
for y := 0; y < dy; y++ {
interpX := scale*(float64(y)+0.5) - 0.5
start[y] = int(interpX) - filterLength/2 + 1
interpX -= float64(start[y])
for i := 0; i < filterLength; i++ {
in := (interpX - float64(i)) * filterFactor
coeffs[y*filterLength+i] = int16(kernel(in) * 256)
}
}
return coeffs, start, filterLength
}
// range [-65536,65536]
func createWeights16(dy, filterLength int, blur, scale float64, kernel func(float64) float64) ([]int32, []int, int) {
filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1))
filterFactor := math.Min(1./(blur*scale), 1)
coeffs := make([]int32, dy*filterLength)
start := make([]int, dy)
for y := 0; y < dy; y++ {
interpX := scale*(float64(y)+0.5) - 0.5
start[y] = int(interpX) - filterLength/2 + 1
interpX -= float64(start[y])
for i := 0; i < filterLength; i++ {
in := (interpX - float64(i)) * filterFactor
coeffs[y*filterLength+i] = int32(kernel(in) * 65536)
}
}
return coeffs, start, filterLength
}
func createWeightsNearest(dy, filterLength int, blur, scale float64) ([]bool, []int, int) {
filterLength = filterLength * int(math.Max(math.Ceil(blur*scale), 1))
filterFactor := math.Min(1./(blur*scale), 1)
coeffs := make([]bool, dy*filterLength)
start := make([]int, dy)
for y := 0; y < dy; y++ {
interpX := scale*(float64(y)+0.5) - 0.5
start[y] = int(interpX) - filterLength/2 + 1
interpX -= float64(start[y])
for i := 0; i < filterLength; i++ {
in := (interpX - float64(i)) * filterFactor
if in >= -0.5 && in < 0.5 {
coeffs[y*filterLength+i] = true
} else {
coeffs[y*filterLength+i] = false
}
}
}
return coeffs, start, filterLength
}

View file

@ -1,318 +0,0 @@
/*
Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/
package resize
import "image"
func floatToUint8(x float32) uint8 {
// Nearest-neighbor values are always
// positive no need to check lower-bound.
if x > 0xfe {
return 0xff
}
return uint8(x)
}
func floatToUint16(x float32) uint16 {
if x > 0xfffe {
return 0xffff
}
return uint16(x)
}
func nearestGeneric(in image.Image, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case xi < 0:
xi = 0
case xi >= maxX:
xi = maxX
}
r, g, b, a := in.At(xi+in.Bounds().Min.X, x+in.Bounds().Min.Y).RGBA()
rgba[0] += float32(r)
rgba[1] += float32(g)
rgba[2] += float32(b)
rgba[3] += float32(a)
sum++
}
}
offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
value := floatToUint16(rgba[0] / sum)
out.Pix[offset+0] = uint8(value >> 8)
out.Pix[offset+1] = uint8(value)
value = floatToUint16(rgba[1] / sum)
out.Pix[offset+2] = uint8(value >> 8)
out.Pix[offset+3] = uint8(value)
value = floatToUint16(rgba[2] / sum)
out.Pix[offset+4] = uint8(value >> 8)
out.Pix[offset+5] = uint8(value)
value = floatToUint16(rgba[3] / sum)
out.Pix[offset+6] = uint8(value >> 8)
out.Pix[offset+7] = uint8(value)
}
}
}
func nearestRGBA(in *image.RGBA, out *image.RGBA, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 4
case xi >= maxX:
xi = 4 * maxX
default:
xi = 0
}
rgba[0] += float32(row[xi+0])
rgba[1] += float32(row[xi+1])
rgba[2] += float32(row[xi+2])
rgba[3] += float32(row[xi+3])
sum++
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4
out.Pix[xo+0] = floatToUint8(rgba[0] / sum)
out.Pix[xo+1] = floatToUint8(rgba[1] / sum)
out.Pix[xo+2] = floatToUint8(rgba[2] / sum)
out.Pix[xo+3] = floatToUint8(rgba[3] / sum)
}
}
}
func nearestNRGBA(in *image.NRGBA, out *image.NRGBA, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 4
case xi >= maxX:
xi = 4 * maxX
default:
xi = 0
}
rgba[0] += float32(row[xi+0])
rgba[1] += float32(row[xi+1])
rgba[2] += float32(row[xi+2])
rgba[3] += float32(row[xi+3])
sum++
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4
out.Pix[xo+0] = floatToUint8(rgba[0] / sum)
out.Pix[xo+1] = floatToUint8(rgba[1] / sum)
out.Pix[xo+2] = floatToUint8(rgba[2] / sum)
out.Pix[xo+3] = floatToUint8(rgba[3] / sum)
}
}
}
func nearestRGBA64(in *image.RGBA64, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 8
case xi >= maxX:
xi = 8 * maxX
default:
xi = 0
}
rgba[0] += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1]))
rgba[1] += float32(uint16(row[xi+2])<<8 | uint16(row[xi+3]))
rgba[2] += float32(uint16(row[xi+4])<<8 | uint16(row[xi+5]))
rgba[3] += float32(uint16(row[xi+6])<<8 | uint16(row[xi+7]))
sum++
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
value := floatToUint16(rgba[0] / sum)
out.Pix[xo+0] = uint8(value >> 8)
out.Pix[xo+1] = uint8(value)
value = floatToUint16(rgba[1] / sum)
out.Pix[xo+2] = uint8(value >> 8)
out.Pix[xo+3] = uint8(value)
value = floatToUint16(rgba[2] / sum)
out.Pix[xo+4] = uint8(value >> 8)
out.Pix[xo+5] = uint8(value)
value = floatToUint16(rgba[3] / sum)
out.Pix[xo+6] = uint8(value >> 8)
out.Pix[xo+7] = uint8(value)
}
}
}
func nearestNRGBA64(in *image.NRGBA64, out *image.NRGBA64, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var rgba [4]float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 8
case xi >= maxX:
xi = 8 * maxX
default:
xi = 0
}
rgba[0] += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1]))
rgba[1] += float32(uint16(row[xi+2])<<8 | uint16(row[xi+3]))
rgba[2] += float32(uint16(row[xi+4])<<8 | uint16(row[xi+5]))
rgba[3] += float32(uint16(row[xi+6])<<8 | uint16(row[xi+7]))
sum++
}
}
xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
value := floatToUint16(rgba[0] / sum)
out.Pix[xo+0] = uint8(value >> 8)
out.Pix[xo+1] = uint8(value)
value = floatToUint16(rgba[1] / sum)
out.Pix[xo+2] = uint8(value >> 8)
out.Pix[xo+3] = uint8(value)
value = floatToUint16(rgba[2] / sum)
out.Pix[xo+4] = uint8(value >> 8)
out.Pix[xo+5] = uint8(value)
value = floatToUint16(rgba[3] / sum)
out.Pix[xo+6] = uint8(value >> 8)
out.Pix[xo+7] = uint8(value)
}
}
}
func nearestGray(in *image.Gray, out *image.Gray, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var gray float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case xi < 0:
xi = 0
case xi >= maxX:
xi = maxX
}
gray += float32(row[xi])
sum++
}
}
offset := (y-newBounds.Min.Y)*out.Stride + (x - newBounds.Min.X)
out.Pix[offset] = floatToUint8(gray / sum)
}
}
}
func nearestGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []bool, offset []int, filterLength int) {
newBounds := out.Bounds()
maxX := in.Bounds().Dx() - 1
for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
row := in.Pix[x*in.Stride:]
for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
var gray float32
var sum float32
start := offset[y]
ci := y * filterLength
for i := 0; i < filterLength; i++ {
if coeffs[ci+i] {
xi := start + i
switch {
case uint(xi) < uint(maxX):
xi *= 2
case xi >= maxX:
xi = 2 * maxX
default:
xi = 0
}
gray += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1]))
sum++
}
}
offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*2
value := floatToUint16(gray / sum)
out.Pix[offset+0] = uint8(value >> 8)
out.Pix[offset+1] = uint8(value)
}
}
}

View file

@ -1,57 +0,0 @@
/*
Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/
package resize
import "testing"
func Test_FloatToUint8(t *testing.T) {
var testData = []struct {
in float32
expected uint8
}{
{0, 0},
{255, 255},
{128, 128},
{1, 1},
{256, 255},
}
for _, test := range testData {
actual := floatToUint8(test.in)
if actual != test.expected {
t.Fail()
}
}
}
func Test_FloatToUint16(t *testing.T) {
var testData = []struct {
in float32
expected uint16
}{
{0, 0},
{65535, 65535},
{128, 128},
{1, 1},
{65536, 65535},
}
for _, test := range testData {
actual := floatToUint16(test.in)
if actual != test.expected {
t.Fail()
}
}
}

View file

@ -1,614 +0,0 @@
/*
Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/
// Package resize implements various image resizing methods.
//
// The package works with the Image interface described in the image package.
// Various interpolation methods are provided and multiple processors may be
// utilized in the computations.
//
// Example:
// imgResized := resize.Resize(1000, 0, imgOld, resize.MitchellNetravali)
package resize
import (
"image"
"runtime"
"sync"
)
// An InterpolationFunction provides the parameters that describe an
// interpolation kernel. It returns the number of samples to take
// and the kernel function to use for sampling.
type InterpolationFunction int
// InterpolationFunction constants
const (
// Nearest-neighbor interpolation
NearestNeighbor InterpolationFunction = iota
// Bilinear interpolation
Bilinear
// Bicubic interpolation (with cubic hermite spline)
Bicubic
// Mitchell-Netravali interpolation
MitchellNetravali
// Lanczos interpolation (a=2)
Lanczos2
// Lanczos interpolation (a=3)
Lanczos3
)
// kernal, returns an InterpolationFunctions taps and kernel.
func (i InterpolationFunction) kernel() (int, func(float64) float64) {
switch i {
case Bilinear:
return 2, linear
case Bicubic:
return 4, cubic
case MitchellNetravali:
return 4, mitchellnetravali
case Lanczos2:
return 4, lanczos2
case Lanczos3:
return 6, lanczos3
default:
// Default to NearestNeighbor.
return 2, nearest
}
}
// values <1 will sharpen the image
var blur = 1.0
// Resize scales an image to new width and height using the interpolation function interp.
// A new image with the given dimensions will be returned.
// If one of the parameters width or height is set to 0, its size will be calculated so that
// the aspect ratio is that of the originating image.
// The resizing algorithm uses channels for parallel computation.
func Resize(width, height uint, img image.Image, interp InterpolationFunction) image.Image {
scaleX, scaleY := calcFactors(width, height, float64(img.Bounds().Dx()), float64(img.Bounds().Dy()))
if width == 0 {
width = uint(0.7 + float64(img.Bounds().Dx())/scaleX)
}
if height == 0 {
height = uint(0.7 + float64(img.Bounds().Dy())/scaleY)
}
// Trivial case: return input image
if int(width) == img.Bounds().Dx() && int(height) == img.Bounds().Dy() {
return img
}
if interp == NearestNeighbor {
return resizeNearest(width, height, scaleX, scaleY, img, interp)
}
taps, kernel := interp.kernel()
cpus := runtime.GOMAXPROCS(0)
wg := sync.WaitGroup{}
// Generic access to image.Image is slow in tight loops.
// The optimal access has to be determined from the concrete image type.
switch input := img.(type) {
case *image.RGBA:
// 8-bit precision
temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
result := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.RGBA)
go func() {
defer wg.Done()
resizeRGBA(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.RGBA)
go func() {
defer wg.Done()
resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.NRGBA:
// 8-bit precision
temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
result := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.RGBA)
go func() {
defer wg.Done()
resizeNRGBA(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.RGBA)
go func() {
defer wg.Done()
resizeRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.YCbCr:
// 8-bit precision
// accessing the YCbCr arrays in a tight loop is slow.
// converting the image to ycc increases performance by 2x.
temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio)
result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444)
coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
in := imageYCbCrToYCC(input)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*ycc)
go func() {
defer wg.Done()
resizeYCbCr(in, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*ycc)
go func() {
defer wg.Done()
resizeYCbCr(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result.YCbCr()
case *image.RGBA64:
// 16-bit precision
temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.RGBA64)
go func() {
defer wg.Done()
resizeRGBA64(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.RGBA64)
go func() {
defer wg.Done()
resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.NRGBA64:
// 16-bit precision
temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.RGBA64)
go func() {
defer wg.Done()
resizeNRGBA64(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.RGBA64)
go func() {
defer wg.Done()
resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.Gray:
// 8-bit precision
temp := image.NewGray(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
result := image.NewGray(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeights8(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.Gray)
go func() {
defer wg.Done()
resizeGray(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeights8(result.Bounds().Dy(), taps, blur, scaleY, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.Gray)
go func() {
defer wg.Done()
resizeGray(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.Gray16:
// 16-bit precision
temp := image.NewGray16(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
result := image.NewGray16(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.Gray16)
go func() {
defer wg.Done()
resizeGray16(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.Gray16)
go func() {
defer wg.Done()
resizeGray16(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
default:
// 16-bit precision
temp := image.NewRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width)))
result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeights16(temp.Bounds().Dy(), taps, blur, scaleX, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.RGBA64)
go func() {
defer wg.Done()
resizeGeneric(img, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeights16(result.Bounds().Dy(), taps, blur, scaleY, kernel)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.RGBA64)
go func() {
defer wg.Done()
resizeRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
}
}
func resizeNearest(width, height uint, scaleX, scaleY float64, img image.Image, interp InterpolationFunction) image.Image {
taps, _ := interp.kernel()
cpus := runtime.GOMAXPROCS(0)
wg := sync.WaitGroup{}
switch input := img.(type) {
case *image.RGBA:
// 8-bit precision
temp := image.NewRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
result := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.RGBA)
go func() {
defer wg.Done()
nearestRGBA(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.RGBA)
go func() {
defer wg.Done()
nearestRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.NRGBA:
// 8-bit precision
temp := image.NewNRGBA(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
result := image.NewNRGBA(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.NRGBA)
go func() {
defer wg.Done()
nearestNRGBA(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.NRGBA)
go func() {
defer wg.Done()
nearestNRGBA(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.YCbCr:
// 8-bit precision
// accessing the YCbCr arrays in a tight loop is slow.
// converting the image to ycc increases performance by 2x.
temp := newYCC(image.Rect(0, 0, input.Bounds().Dy(), int(width)), input.SubsampleRatio)
result := newYCC(image.Rect(0, 0, int(width), int(height)), image.YCbCrSubsampleRatio444)
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
in := imageYCbCrToYCC(input)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*ycc)
go func() {
defer wg.Done()
nearestYCbCr(in, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*ycc)
go func() {
defer wg.Done()
nearestYCbCr(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result.YCbCr()
case *image.RGBA64:
// 16-bit precision
temp := image.NewRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.RGBA64)
go func() {
defer wg.Done()
nearestRGBA64(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.RGBA64)
go func() {
defer wg.Done()
nearestRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.NRGBA64:
// 16-bit precision
temp := image.NewNRGBA64(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
result := image.NewNRGBA64(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.NRGBA64)
go func() {
defer wg.Done()
nearestNRGBA64(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.NRGBA64)
go func() {
defer wg.Done()
nearestNRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.Gray:
// 8-bit precision
temp := image.NewGray(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
result := image.NewGray(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.Gray)
go func() {
defer wg.Done()
nearestGray(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.Gray)
go func() {
defer wg.Done()
nearestGray(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
case *image.Gray16:
// 16-bit precision
temp := image.NewGray16(image.Rect(0, 0, input.Bounds().Dy(), int(width)))
result := image.NewGray16(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.Gray16)
go func() {
defer wg.Done()
nearestGray16(input, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.Gray16)
go func() {
defer wg.Done()
nearestGray16(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
default:
// 16-bit precision
temp := image.NewRGBA64(image.Rect(0, 0, img.Bounds().Dy(), int(width)))
result := image.NewRGBA64(image.Rect(0, 0, int(width), int(height)))
// horizontal filter, results in transposed temporary image
coeffs, offset, filterLength := createWeightsNearest(temp.Bounds().Dy(), taps, blur, scaleX)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(temp, i, cpus).(*image.RGBA64)
go func() {
defer wg.Done()
nearestGeneric(img, slice, scaleX, coeffs, offset, filterLength)
}()
}
wg.Wait()
// horizontal filter on transposed image, result is not transposed
coeffs, offset, filterLength = createWeightsNearest(result.Bounds().Dy(), taps, blur, scaleY)
wg.Add(cpus)
for i := 0; i < cpus; i++ {
slice := makeSlice(result, i, cpus).(*image.RGBA64)
go func() {
defer wg.Done()
nearestRGBA64(temp, slice, scaleY, coeffs, offset, filterLength)
}()
}
wg.Wait()
return result
}
}
// Calculates scaling factors using old and new image dimensions.
func calcFactors(width, height uint, oldWidth, oldHeight float64) (scaleX, scaleY float64) {
if width == 0 {
if height == 0 {
scaleX = 1.0
scaleY = 1.0
} else {
scaleY = oldHeight / float64(height)
scaleX = scaleY
}
} else {
scaleX = oldWidth / float64(width)
if height == 0 {
scaleY = scaleX
} else {
scaleY = oldHeight / float64(height)
}
}
return
}
type imageWithSubImage interface {
image.Image
SubImage(image.Rectangle) image.Image
}
func makeSlice(img imageWithSubImage, i, n int) image.Image {
return img.SubImage(image.Rect(img.Bounds().Min.X, img.Bounds().Min.Y+i*img.Bounds().Dy()/n, img.Bounds().Max.X, img.Bounds().Min.Y+(i+1)*img.Bounds().Dy()/n))
}

View file

@ -1,330 +0,0 @@
package resize
import (
"image"
"image/color"
"runtime"
"testing"
)
var img = image.NewGray16(image.Rect(0, 0, 3, 3))
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
img.Set(1, 1, color.White)
}
func Test_Param1(t *testing.T) {
m := Resize(0, 0, img, NearestNeighbor)
if m.Bounds() != img.Bounds() {
t.Fail()
}
}
func Test_Param2(t *testing.T) {
m := Resize(100, 0, img, NearestNeighbor)
if m.Bounds() != image.Rect(0, 0, 100, 100) {
t.Fail()
}
}
func Test_ZeroImg(t *testing.T) {
zeroImg := image.NewGray16(image.Rect(0, 0, 0, 0))
m := Resize(0, 0, zeroImg, NearestNeighbor)
if m.Bounds() != zeroImg.Bounds() {
t.Fail()
}
}
func Test_CorrectResize(t *testing.T) {
zeroImg := image.NewGray16(image.Rect(0, 0, 256, 256))
m := Resize(60, 0, zeroImg, NearestNeighbor)
if m.Bounds() != image.Rect(0, 0, 60, 60) {
t.Fail()
}
}
func Test_SameColorWithRGBA(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 20, 20))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
img.SetRGBA(x, y, color.RGBA{0x80, 0x80, 0x80, 0xFF})
}
}
out := Resize(10, 10, img, Lanczos3)
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
color := out.At(x, y).(color.RGBA)
if color.R != 0x80 || color.G != 0x80 || color.B != 0x80 || color.A != 0xFF {
t.Errorf("%+v", color)
}
}
}
}
func Test_SameColorWithNRGBA(t *testing.T) {
img := image.NewNRGBA(image.Rect(0, 0, 20, 20))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
img.SetNRGBA(x, y, color.NRGBA{0x80, 0x80, 0x80, 0xFF})
}
}
out := Resize(10, 10, img, Lanczos3)
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
color := out.At(x, y).(color.RGBA)
if color.R != 0x80 || color.G != 0x80 || color.B != 0x80 || color.A != 0xFF {
t.Errorf("%+v", color)
}
}
}
}
func Test_SameColorWithRGBA64(t *testing.T) {
img := image.NewRGBA64(image.Rect(0, 0, 20, 20))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
img.SetRGBA64(x, y, color.RGBA64{0x8000, 0x8000, 0x8000, 0xFFFF})
}
}
out := Resize(10, 10, img, Lanczos3)
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
color := out.At(x, y).(color.RGBA64)
if color.R != 0x8000 || color.G != 0x8000 || color.B != 0x8000 || color.A != 0xFFFF {
t.Errorf("%+v", color)
}
}
}
}
func Test_SameColorWithNRGBA64(t *testing.T) {
img := image.NewNRGBA64(image.Rect(0, 0, 20, 20))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
img.SetNRGBA64(x, y, color.NRGBA64{0x8000, 0x8000, 0x8000, 0xFFFF})
}
}
out := Resize(10, 10, img, Lanczos3)
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
color := out.At(x, y).(color.RGBA64)
if color.R != 0x8000 || color.G != 0x8000 || color.B != 0x8000 || color.A != 0xFFFF {
t.Errorf("%+v", color)
}
}
}
}
func Test_SameColorWithGray(t *testing.T) {
img := image.NewGray(image.Rect(0, 0, 20, 20))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
img.SetGray(x, y, color.Gray{0x80})
}
}
out := Resize(10, 10, img, Lanczos3)
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
color := out.At(x, y).(color.Gray)
if color.Y != 0x80 {
t.Errorf("%+v", color)
}
}
}
}
func Test_SameColorWithGray16(t *testing.T) {
img := image.NewGray16(image.Rect(0, 0, 20, 20))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
img.SetGray16(x, y, color.Gray16{0x8000})
}
}
out := Resize(10, 10, img, Lanczos3)
for y := out.Bounds().Min.Y; y < out.Bounds().Max.Y; y++ {
for x := out.Bounds().Min.X; x < out.Bounds().Max.X; x++ {
color := out.At(x, y).(color.Gray16)
if color.Y != 0x8000 {
t.Errorf("%+v", color)
}
}
}
}
func Test_Bounds(t *testing.T) {
img := image.NewRGBA(image.Rect(20, 10, 200, 99))
out := Resize(80, 80, img, Lanczos2)
out.At(0, 0)
}
func Test_SameSizeReturnsOriginal(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 10, 10))
out := Resize(0, 0, img, Lanczos2)
if img != out {
t.Fail()
}
out = Resize(10, 10, img, Lanczos2)
if img != out {
t.Fail()
}
}
func Test_PixelCoordinates(t *testing.T) {
checkers := image.NewGray(image.Rect(0, 0, 4, 4))
checkers.Pix = []uint8{
255, 0, 255, 0,
0, 255, 0, 255,
255, 0, 255, 0,
0, 255, 0, 255,
}
resized := Resize(12, 12, checkers, NearestNeighbor).(*image.Gray)
if resized.Pix[0] != 255 || resized.Pix[1] != 255 || resized.Pix[2] != 255 {
t.Fail()
}
if resized.Pix[3] != 0 || resized.Pix[4] != 0 || resized.Pix[5] != 0 {
t.Fail()
}
}
func Test_ResizeWithPremultipliedAlpha(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 1, 4))
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
// 0x80 = 0.5 * 0xFF.
img.SetRGBA(0, y, color.RGBA{0x80, 0x80, 0x80, 0x80})
}
out := Resize(1, 2, img, MitchellNetravali)
outputColor := out.At(0, 0).(color.RGBA)
if outputColor.R != 0x80 {
t.Fail()
}
}
func Test_ResizeWithTranslucentColor(t *testing.T) {
img := image.NewNRGBA(image.Rect(0, 0, 1, 2))
// Set the pixel colors to an "invisible green" and white.
// After resizing, the green shouldn't be visible.
img.SetNRGBA(0, 0, color.NRGBA{0x00, 0xFF, 0x00, 0x00})
img.SetNRGBA(0, 1, color.NRGBA{0x00, 0x00, 0x00, 0xFF})
out := Resize(1, 1, img, Bilinear)
_, g, _, _ := out.At(0, 0).RGBA()
if g != 0x00 {
t.Errorf("%+v", g)
}
}
const (
// Use a small image size for benchmarks. We don't want memory performance
// to affect the benchmark results.
benchMaxX = 250
benchMaxY = 250
// Resize values near the original size require increase the amount of time
// resize spends converting the image.
benchWidth = 200
benchHeight = 200
)
func benchRGBA(b *testing.B, interp InterpolationFunction) {
m := image.NewRGBA(image.Rect(0, 0, benchMaxX, benchMaxY))
// Initialize m's pixels to create a non-uniform image.
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
i := m.PixOffset(x, y)
m.Pix[i+0] = uint8(y + 4*x)
m.Pix[i+1] = uint8(y + 4*x)
m.Pix[i+2] = uint8(y + 4*x)
m.Pix[i+3] = uint8(4*y + x)
}
}
var out image.Image
b.ResetTimer()
for i := 0; i < b.N; i++ {
out = Resize(benchWidth, benchHeight, m, interp)
}
out.At(0, 0)
}
// The names of some interpolation functions are truncated so that the columns
// of 'go test -bench' line up.
func Benchmark_Nearest_RGBA(b *testing.B) {
benchRGBA(b, NearestNeighbor)
}
func Benchmark_Bilinear_RGBA(b *testing.B) {
benchRGBA(b, Bilinear)
}
func Benchmark_Bicubic_RGBA(b *testing.B) {
benchRGBA(b, Bicubic)
}
func Benchmark_Mitchell_RGBA(b *testing.B) {
benchRGBA(b, MitchellNetravali)
}
func Benchmark_Lanczos2_RGBA(b *testing.B) {
benchRGBA(b, Lanczos2)
}
func Benchmark_Lanczos3_RGBA(b *testing.B) {
benchRGBA(b, Lanczos3)
}
func benchYCbCr(b *testing.B, interp InterpolationFunction) {
m := image.NewYCbCr(image.Rect(0, 0, benchMaxX, benchMaxY), image.YCbCrSubsampleRatio422)
// Initialize m's pixels to create a non-uniform image.
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
yi := m.YOffset(x, y)
ci := m.COffset(x, y)
m.Y[yi] = uint8(16*y + x)
m.Cb[ci] = uint8(y + 16*x)
m.Cr[ci] = uint8(y + 16*x)
}
}
var out image.Image
b.ResetTimer()
for i := 0; i < b.N; i++ {
out = Resize(benchWidth, benchHeight, m, interp)
}
out.At(0, 0)
}
func Benchmark_Nearest_YCC(b *testing.B) {
benchYCbCr(b, NearestNeighbor)
}
func Benchmark_Bilinear_YCC(b *testing.B) {
benchYCbCr(b, Bilinear)
}
func Benchmark_Bicubic_YCC(b *testing.B) {
benchYCbCr(b, Bicubic)
}
func Benchmark_Mitchell_YCC(b *testing.B) {
benchYCbCr(b, MitchellNetravali)
}
func Benchmark_Lanczos2_YCC(b *testing.B) {
benchYCbCr(b, Lanczos2)
}
func Benchmark_Lanczos3_YCC(b *testing.B) {
benchYCbCr(b, Lanczos3)
}

View file

@ -1,55 +0,0 @@
/*
Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/
package resize
import (
"image"
)
// Thumbnail will downscale provided image to max width and height preserving
// original aspect ratio and using the interpolation function interp.
// It will return original image, without processing it, if original sizes
// are already smaller than provided constraints.
func Thumbnail(maxWidth, maxHeight uint, img image.Image, interp InterpolationFunction) image.Image {
origBounds := img.Bounds()
origWidth := uint(origBounds.Dx())
origHeight := uint(origBounds.Dy())
newWidth, newHeight := origWidth, origHeight
// Return original image if it have same or smaller size as constraints
if maxWidth >= origWidth && maxHeight >= origHeight {
return img
}
// Preserve aspect ratio
if origWidth > maxWidth {
newHeight = uint(origHeight * maxWidth / origWidth)
if newHeight < 1 {
newHeight = 1
}
newWidth = maxWidth
}
if newHeight > maxHeight {
newWidth = uint(newWidth * maxHeight / newHeight)
if newWidth < 1 {
newWidth = 1
}
newHeight = maxHeight
}
return Resize(newWidth, newHeight, img, interp)
}

View file

@ -1,47 +0,0 @@
package resize
import (
"image"
"runtime"
"testing"
)
func init() {
runtime.GOMAXPROCS(runtime.NumCPU())
}
var thumbnailTests = []struct {
origWidth int
origHeight int
maxWidth uint
maxHeight uint
expectedWidth uint
expectedHeight uint
}{
{5, 5, 10, 10, 5, 5},
{10, 10, 5, 5, 5, 5},
{10, 50, 10, 10, 2, 10},
{50, 10, 10, 10, 10, 2},
{50, 100, 60, 90, 45, 90},
{120, 100, 60, 90, 60, 50},
{200, 250, 200, 150, 120, 150},
}
func TestThumbnail(t *testing.T) {
for i, tt := range thumbnailTests {
img := image.NewGray16(image.Rect(0, 0, tt.origWidth, tt.origHeight))
outImg := Thumbnail(tt.maxWidth, tt.maxHeight, img, NearestNeighbor)
newWidth := uint(outImg.Bounds().Dx())
newHeight := uint(outImg.Bounds().Dy())
if newWidth != tt.expectedWidth ||
newHeight != tt.expectedHeight {
t.Errorf("%d. Thumbnail(%v, %v, img, NearestNeighbor) => "+
"width: %v, height: %v, want width: %v, height: %v",
i, tt.maxWidth, tt.maxHeight,
newWidth, newHeight, tt.expectedWidth, tt.expectedHeight,
)
}
}
}

227
vendor/github.com/nfnt/resize/ycc.go generated vendored
View file

@ -1,227 +0,0 @@
/*
Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/
package resize
import (
"image"
"image/color"
)
// ycc is an in memory YCbCr image. The Y, Cb and Cr samples are held in a
// single slice to increase resizing performance.
type ycc struct {
// Pix holds the image's pixels, in Y, Cb, Cr order. The pixel at
// (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3].
Pix []uint8
// Stride is the Pix stride (in bytes) between vertically adjacent pixels.
Stride int
// Rect is the image's bounds.
Rect image.Rectangle
// SubsampleRatio is the subsample ratio of the original YCbCr image.
SubsampleRatio image.YCbCrSubsampleRatio
}
// PixOffset returns the index of the first element of Pix that corresponds to
// the pixel at (x, y).
func (p *ycc) PixOffset(x, y int) int {
return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*3
}
func (p *ycc) Bounds() image.Rectangle {
return p.Rect
}
func (p *ycc) ColorModel() color.Model {
return color.YCbCrModel
}
func (p *ycc) At(x, y int) color.Color {
if !(image.Point{x, y}.In(p.Rect)) {
return color.YCbCr{}
}
i := p.PixOffset(x, y)
return color.YCbCr{
p.Pix[i+0],
p.Pix[i+1],
p.Pix[i+2],
}
}
func (p *ycc) Opaque() bool {
return true
}
// SubImage returns an image representing the portion of the image p visible
// through r. The returned value shares pixels with the original image.
func (p *ycc) SubImage(r image.Rectangle) image.Image {
r = r.Intersect(p.Rect)
if r.Empty() {
return &ycc{SubsampleRatio: p.SubsampleRatio}
}
i := p.PixOffset(r.Min.X, r.Min.Y)
return &ycc{
Pix: p.Pix[i:],
Stride: p.Stride,
Rect: r,
SubsampleRatio: p.SubsampleRatio,
}
}
// newYCC returns a new ycc with the given bounds and subsample ratio.
func newYCC(r image.Rectangle, s image.YCbCrSubsampleRatio) *ycc {
w, h := r.Dx(), r.Dy()
buf := make([]uint8, 3*w*h)
return &ycc{Pix: buf, Stride: 3 * w, Rect: r, SubsampleRatio: s}
}
// YCbCr converts ycc to a YCbCr image with the same subsample ratio
// as the YCbCr image that ycc was generated from.
func (p *ycc) YCbCr() *image.YCbCr {
ycbcr := image.NewYCbCr(p.Rect, p.SubsampleRatio)
var off int
switch ycbcr.SubsampleRatio {
case image.YCbCrSubsampleRatio422:
for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
cy := (y - ycbcr.Rect.Min.Y) * ycbcr.CStride
for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
xx := (x - ycbcr.Rect.Min.X)
yi := yy + xx
ci := cy + xx/2
ycbcr.Y[yi] = p.Pix[off+0]
ycbcr.Cb[ci] = p.Pix[off+1]
ycbcr.Cr[ci] = p.Pix[off+2]
off += 3
}
}
case image.YCbCrSubsampleRatio420:
for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
cy := (y/2 - ycbcr.Rect.Min.Y/2) * ycbcr.CStride
for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
xx := (x - ycbcr.Rect.Min.X)
yi := yy + xx
ci := cy + xx/2
ycbcr.Y[yi] = p.Pix[off+0]
ycbcr.Cb[ci] = p.Pix[off+1]
ycbcr.Cr[ci] = p.Pix[off+2]
off += 3
}
}
case image.YCbCrSubsampleRatio440:
for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
cy := (y/2 - ycbcr.Rect.Min.Y/2) * ycbcr.CStride
for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
xx := (x - ycbcr.Rect.Min.X)
yi := yy + xx
ci := cy + xx
ycbcr.Y[yi] = p.Pix[off+0]
ycbcr.Cb[ci] = p.Pix[off+1]
ycbcr.Cr[ci] = p.Pix[off+2]
off += 3
}
}
default:
// Default to 4:4:4 subsampling.
for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
cy := (y - ycbcr.Rect.Min.Y) * ycbcr.CStride
for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
xx := (x - ycbcr.Rect.Min.X)
yi := yy + xx
ci := cy + xx
ycbcr.Y[yi] = p.Pix[off+0]
ycbcr.Cb[ci] = p.Pix[off+1]
ycbcr.Cr[ci] = p.Pix[off+2]
off += 3
}
}
}
return ycbcr
}
// imageYCbCrToYCC converts a YCbCr image to a ycc image for resizing.
func imageYCbCrToYCC(in *image.YCbCr) *ycc {
w, h := in.Rect.Dx(), in.Rect.Dy()
r := image.Rect(0, 0, w, h)
buf := make([]uint8, 3*w*h)
p := ycc{Pix: buf, Stride: 3 * w, Rect: r, SubsampleRatio: in.SubsampleRatio}
var off int
switch in.SubsampleRatio {
case image.YCbCrSubsampleRatio422:
for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
yy := (y - in.Rect.Min.Y) * in.YStride
cy := (y - in.Rect.Min.Y) * in.CStride
for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
xx := (x - in.Rect.Min.X)
yi := yy + xx
ci := cy + xx/2
p.Pix[off+0] = in.Y[yi]
p.Pix[off+1] = in.Cb[ci]
p.Pix[off+2] = in.Cr[ci]
off += 3
}
}
case image.YCbCrSubsampleRatio420:
for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
yy := (y - in.Rect.Min.Y) * in.YStride
cy := (y/2 - in.Rect.Min.Y/2) * in.CStride
for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
xx := (x - in.Rect.Min.X)
yi := yy + xx
ci := cy + xx/2
p.Pix[off+0] = in.Y[yi]
p.Pix[off+1] = in.Cb[ci]
p.Pix[off+2] = in.Cr[ci]
off += 3
}
}
case image.YCbCrSubsampleRatio440:
for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
yy := (y - in.Rect.Min.Y) * in.YStride
cy := (y/2 - in.Rect.Min.Y/2) * in.CStride
for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
xx := (x - in.Rect.Min.X)
yi := yy + xx
ci := cy + xx
p.Pix[off+0] = in.Y[yi]
p.Pix[off+1] = in.Cb[ci]
p.Pix[off+2] = in.Cr[ci]
off += 3
}
}
default:
// Default to 4:4:4 subsampling.
for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
yy := (y - in.Rect.Min.Y) * in.YStride
cy := (y - in.Rect.Min.Y) * in.CStride
for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
xx := (x - in.Rect.Min.X)
yi := yy + xx
ci := cy + xx
p.Pix[off+0] = in.Y[yi]
p.Pix[off+1] = in.Cb[ci]
p.Pix[off+2] = in.Cr[ci]
off += 3
}
}
}
return &p
}

View file

@ -1,214 +0,0 @@
/*
Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/
package resize
import (
"image"
"image/color"
"testing"
)
type Image interface {
image.Image
SubImage(image.Rectangle) image.Image
}
func TestImage(t *testing.T) {
testImage := []Image{
newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio420),
newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio422),
newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio440),
newYCC(image.Rect(0, 0, 10, 10), image.YCbCrSubsampleRatio444),
}
for _, m := range testImage {
if !image.Rect(0, 0, 10, 10).Eq(m.Bounds()) {
t.Errorf("%T: want bounds %v, got %v",
m, image.Rect(0, 0, 10, 10), m.Bounds())
continue
}
m = m.SubImage(image.Rect(3, 2, 9, 8)).(Image)
if !image.Rect(3, 2, 9, 8).Eq(m.Bounds()) {
t.Errorf("%T: sub-image want bounds %v, got %v",
m, image.Rect(3, 2, 9, 8), m.Bounds())
continue
}
// Test that taking an empty sub-image starting at a corner does not panic.
m.SubImage(image.Rect(0, 0, 0, 0))
m.SubImage(image.Rect(10, 0, 10, 0))
m.SubImage(image.Rect(0, 10, 0, 10))
m.SubImage(image.Rect(10, 10, 10, 10))
}
}
func TestConvertYCbCr(t *testing.T) {
testImage := []Image{
image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio420),
image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio422),
image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio440),
image.NewYCbCr(image.Rect(0, 0, 50, 50), image.YCbCrSubsampleRatio444),
}
for _, img := range testImage {
m := img.(*image.YCbCr)
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
yi := m.YOffset(x, y)
ci := m.COffset(x, y)
m.Y[yi] = uint8(16*y + x)
m.Cb[ci] = uint8(y + 16*x)
m.Cr[ci] = uint8(y + 16*x)
}
}
// test conversion from YCbCr to ycc
yc := imageYCbCrToYCC(m)
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
ystride := 3 * (m.Rect.Max.X - m.Rect.Min.X)
xstride := 3
yi := m.YOffset(x, y)
ci := m.COffset(x, y)
si := (y * ystride) + (x * xstride)
if m.Y[yi] != yc.Pix[si] {
t.Errorf("Err Y - found: %d expected: %d x: %d y: %d yi: %d si: %d",
m.Y[yi], yc.Pix[si], x, y, yi, si)
}
if m.Cb[ci] != yc.Pix[si+1] {
t.Errorf("Err Cb - found: %d expected: %d x: %d y: %d ci: %d si: %d",
m.Cb[ci], yc.Pix[si+1], x, y, ci, si+1)
}
if m.Cr[ci] != yc.Pix[si+2] {
t.Errorf("Err Cr - found: %d expected: %d x: %d y: %d ci: %d si: %d",
m.Cr[ci], yc.Pix[si+2], x, y, ci, si+2)
}
}
}
// test conversion from ycc back to YCbCr
ym := yc.YCbCr()
for y := m.Rect.Min.Y; y < m.Rect.Max.Y; y++ {
for x := m.Rect.Min.X; x < m.Rect.Max.X; x++ {
yi := m.YOffset(x, y)
ci := m.COffset(x, y)
if m.Y[yi] != ym.Y[yi] {
t.Errorf("Err Y - found: %d expected: %d x: %d y: %d yi: %d",
m.Y[yi], ym.Y[yi], x, y, yi)
}
if m.Cb[ci] != ym.Cb[ci] {
t.Errorf("Err Cb - found: %d expected: %d x: %d y: %d ci: %d",
m.Cb[ci], ym.Cb[ci], x, y, ci)
}
if m.Cr[ci] != ym.Cr[ci] {
t.Errorf("Err Cr - found: %d expected: %d x: %d y: %d ci: %d",
m.Cr[ci], ym.Cr[ci], x, y, ci)
}
}
}
}
}
func TestYCbCr(t *testing.T) {
rects := []image.Rectangle{
image.Rect(0, 0, 16, 16),
image.Rect(1, 0, 16, 16),
image.Rect(0, 1, 16, 16),
image.Rect(1, 1, 16, 16),
image.Rect(1, 1, 15, 16),
image.Rect(1, 1, 16, 15),
image.Rect(1, 1, 15, 15),
image.Rect(2, 3, 14, 15),
image.Rect(7, 0, 7, 16),
image.Rect(0, 8, 16, 8),
image.Rect(0, 0, 10, 11),
image.Rect(5, 6, 16, 16),
image.Rect(7, 7, 8, 8),
image.Rect(7, 8, 8, 9),
image.Rect(8, 7, 9, 8),
image.Rect(8, 8, 9, 9),
image.Rect(7, 7, 17, 17),
image.Rect(8, 8, 17, 17),
image.Rect(9, 9, 17, 17),
image.Rect(10, 10, 17, 17),
}
subsampleRatios := []image.YCbCrSubsampleRatio{
image.YCbCrSubsampleRatio444,
image.YCbCrSubsampleRatio422,
image.YCbCrSubsampleRatio420,
image.YCbCrSubsampleRatio440,
}
deltas := []image.Point{
image.Pt(0, 0),
image.Pt(1000, 1001),
image.Pt(5001, -400),
image.Pt(-701, -801),
}
for _, r := range rects {
for _, subsampleRatio := range subsampleRatios {
for _, delta := range deltas {
testYCbCr(t, r, subsampleRatio, delta)
}
}
if testing.Short() {
break
}
}
}
func testYCbCr(t *testing.T, r image.Rectangle, subsampleRatio image.YCbCrSubsampleRatio, delta image.Point) {
// Create a YCbCr image m, whose bounds are r translated by (delta.X, delta.Y).
r1 := r.Add(delta)
img := image.NewYCbCr(r1, subsampleRatio)
// Initialize img's pixels. For 422 and 420 subsampling, some of the Cb and Cr elements
// will be set multiple times. That's OK. We just want to avoid a uniform image.
for y := r1.Min.Y; y < r1.Max.Y; y++ {
for x := r1.Min.X; x < r1.Max.X; x++ {
yi := img.YOffset(x, y)
ci := img.COffset(x, y)
img.Y[yi] = uint8(16*y + x)
img.Cb[ci] = uint8(y + 16*x)
img.Cr[ci] = uint8(y + 16*x)
}
}
m := imageYCbCrToYCC(img)
// Make various sub-images of m.
for y0 := delta.Y + 3; y0 < delta.Y+7; y0++ {
for y1 := delta.Y + 8; y1 < delta.Y+13; y1++ {
for x0 := delta.X + 3; x0 < delta.X+7; x0++ {
for x1 := delta.X + 8; x1 < delta.X+13; x1++ {
subRect := image.Rect(x0, y0, x1, y1)
sub := m.SubImage(subRect).(*ycc)
// For each point in the sub-image's bounds, check that m.At(x, y) equals sub.At(x, y).
for y := sub.Rect.Min.Y; y < sub.Rect.Max.Y; y++ {
for x := sub.Rect.Min.X; x < sub.Rect.Max.X; x++ {
color0 := m.At(x, y).(color.YCbCr)
color1 := sub.At(x, y).(color.YCbCr)
if color0 != color1 {
t.Errorf("r=%v, subsampleRatio=%v, delta=%v, x=%d, y=%d, color0=%v, color1=%v",
r, subsampleRatio, delta, x, y, color0, color1)
return
}
}
}
}
}
}
}
}

View file

@ -1,7 +0,0 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- tip

View file

@ -1,14 +0,0 @@
Copyright (c) 2009, <iiasija>
Copyright (c) 2013-2014 Naoki OKAMURA (Nyarla) <nyarla@thotep.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,42 +0,0 @@
crypt
=====
A golang implementation of crypt(3).
[![Build Status](https://travis-ci.org/nyarla/go-crypt.svg?branch=master)](https://travis-ci.org/nyarla/go-crypt) [![GoDoc](http://godoc.org/github.com/nyarla/go-crypt?status.svg)](https://godoc.org/github.com/nyarla/go-crypt)
EXAMPLES CODE
-------------
```go
import (
"fmt"
"github.com/nyarlabo/go-crypt"
)
func main() {
fmt.Println(crypt.Crypt("testtest", "es")); // esDRYJnY4VaGM
}
```
WHY I FROKED IT?
----------------
Original implementation is writte by iasija at 2009-12-08,
and original implementation is not supported golang 1.1 or later.
So I fork it for fix this issue, and I added documenation and test code.
Original implementation is hosting on [code.google.com/p/go-crypt](https://code.google.com/p/go-crypt),
and that source code is under the 3-Clause BSD.
NOTE: I could't find to iasija's contact address.
COPYRIGTS AND LICENSE
---------------------
1. Original Implementation: Copyright (c) 2009 iasija All Rights Reserved. ([BSD-3-Clause](http://opensource.org/licenses/BSD-3-Clause))
2. Modification Codes: Copyright (c) 22013-2015 Naoki OKAMURA a.k.a nyarla <nyarla@thotep.net> Some Rights Reserved. ([BSD-3-Clause](http://opensource.org/licenses/BSD-3-Clause))

View file

@ -1,276 +0,0 @@
package crypt
import (
"bytes"
)
var PC1_C = []byte{
57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
}
var PC1_D = []byte{
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4,
}
var PC2_C = []byte{
14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
}
var PC2_D = []byte{
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32,
}
var e2 = []byte{
32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1,
}
var IP = []byte{
58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7,
}
var FP = []byte{
40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25,
}
var S = [][]byte{
[]byte{14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13},
[]byte{15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9},
[]byte{10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12},
[]byte{7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14},
[]byte{2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3},
[]byte{12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13},
[]byte{4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12},
[]byte{13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11},
}
var P = []byte{
16, 7, 20, 21, 29, 12, 28, 17,
1, 15, 23, 26, 5, 18, 31, 10,
2, 8, 24, 14, 32, 27, 3, 9,
19, 13, 30, 6, 22, 11, 4, 25,
}
var shift = []int{1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1}
// Crypt is a implementation of crypt(3) like as perl or ruby and ...etc.
//
// Behavior of this func is same as perl-5.18.2's crypt on OSX Yosemite.
func Crypt(pw string, salt string) string {
if sLen := len(salt); sLen < 2 {
src := []byte(salt)
for len(src) < 2 {
src = append(src, 0x00)
}
salt = string(src)
}
block := make([]byte, 66)
for i := 0; i < 8 && i < len(pw); i++ {
for j := 0; j < 7; j++ {
block[(8*i)+j] = (pw[i] >> byte(6-j)) & 1
}
}
C := make([]byte, 28)
D := make([]byte, 28)
for i := 0; i < 28; i++ {
C[i] = block[PC1_C[i]-1]
D[i] = block[PC1_D[i]-1]
}
KS := make([][]byte, 16)
for i := 0; i < 16; i++ {
KS[i] = make([]byte, 48)
}
for i := 0; i < 16; i++ {
for k := 0; k < shift[i]; k++ {
t := C[0]
for j := 0; j < 28-1; j++ {
C[j] = C[j+1]
}
C[27] = t
t = D[0]
for j := 0; j < 28-1; j++ {
D[j] = D[j+1]
}
D[27] = t
}
for j := 0; j < 24; j++ {
KS[i][j] = C[PC2_C[j]-1]
KS[i][j+24] = D[PC2_D[j]-28-1]
}
}
E := make([]byte, 48)
for i := 0; i < 48; i++ {
E[i] = e2[i]
}
iobuf := make([]byte, 16)
for i := 0; i < 2; i++ {
c := byte(salt[i])
iobuf[i] = c
if c > 'Z' {
c -= 6
}
if c > '9' {
c -= 7
}
c -= '.'
for j := 0; j < 6; j++ {
if (c>>byte(j))&1 != 0 {
k := E[6*i+j]
E[6*i+j] = E[6*i+j+24]
E[6*i+j+24] = k
}
}
}
for i := 0; i < 66; i++ {
block[i] = 0
}
R := make([]byte, 32)
L := make([]byte, 32)
DMY := make([]byte, 32)
preS := make([]byte, 48)
f := make([]byte, 32)
dmy_block := make([]byte, 64)
for m := 0; m < 25; m++ {
for i := 0; i < 32; i++ {
L[i] = block[IP[i]-1]
}
for i := 32; i < 64; i++ {
R[i-32] = block[IP[i]-1]
}
for i := 0; i < 16; i++ {
for j := 0; j < 32; j++ {
DMY[j] = R[j]
}
for j := 0; j < 48; j++ {
preS[j] = R[E[j]-1] ^ KS[i][j]
}
for j := 0; j < 8; j++ {
t := 6 * j
k := S[j][(preS[t+0]<<5)+
(preS[t+1]<<3)+
(preS[t+2]<<2)+
(preS[t+3]<<1)+
(preS[t+4]<<0)+
(preS[t+5]<<4)]
t = 4 * j
f[t+0] = (k >> 3) & 01
f[t+1] = (k >> 2) & 01
f[t+2] = (k >> 1) & 01
f[t+3] = (k >> 0) & 01
}
for j := 0; j < 32; j++ {
R[j] = L[j] ^ f[P[j]-1]
}
for j := 0; j < 32; j++ {
L[j] = DMY[j]
}
}
for i := 0; i < 32; i++ {
L[i], R[i] = R[i], L[i]
}
for i := 0; i < 32; i++ {
dmy_block[i] = L[i]
}
for i := 32; i < 64; i++ {
dmy_block[i] = R[i-32]
}
for i := 0; i < 64; i++ {
block[i] = dmy_block[FP[i]-1]
}
}
var i int
for i = 0; i < 11; i++ {
c := byte(0)
for j := 0; j < 6; j++ {
c = c << 1
c = c | block[6*i+j]
}
c = c + '.'
if c > '9' {
c += 7
}
if c > 'Z' {
c += 6
}
iobuf[i+2] = c
}
iobuf[i+2] = 0
return string(bytes.Replace(iobuf, []byte{0x00}, []byte{}, -1))
}

View file

@ -1,18 +0,0 @@
package crypt
import (
"fmt"
"testing"
)
func TestCrypt(t *testing.T) {
if ret := Crypt("testtest", "es"); ret != `esDRYJnY4VaGM` {
t.Fatal(fmt.Sprintf(`result of Crypt is musmatch: %+v`, []byte(ret)))
}
}
func ExampleCrypt() {
fmt.Println(Crypt("testtest", "es"))
// Output:
// esDRYJnY4VaGM
}

View file

@ -1,11 +0,0 @@
// Package crypt is a implementation of crypt(3) by golang.
//
// This is a fork of iasija's orignal implementation.
//
// Orignal soruce code Copyrights (C) iasija All rights reserved,
// and original source code is under the 3-Clause BSD.
//
// Modification codes for supporting latest golang and added test codes are
// Copyright (c) 2013-2014 Naoki OKAMURA <nyarla@thotep.net>,
// and modifiration code and test codes are under same as the orignal source code license. (3-Clause BSD)
package crypt

View file

@ -4,12 +4,17 @@ import (
"io"
)
var ApiVersionRouting = map[string]struct{AuthFunction; DispatchFunction}{
const VERSION = 0.3
var ApiVersionRouting = map[string]struct {
AuthFunction
DispatchFunction
}{
"GET": {PublicPage, showVersion},
}
func showVersion(u *User, args []string, body io.ReadCloser) (interface{}, error) {
m := map[string]interface{}{"version": 0.1}
m := map[string]interface{}{"version": VERSION}
if u != nil {
m["youare"] = *u