Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ikawaha committed Apr 27, 2016
0 parents commit 4b9d88c
Show file tree
Hide file tree
Showing 38 changed files with 1,448 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2016 ikawaha

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
waifu2x.go
===

[![experimental](http://badges.github.io/stability-badges/dist/experimental.svg)](http://github.com/badges/stability-badges)

Image super-resolution using deep convolutional neural network (CNN).
waifu2x.go is a clone of waifu2x-js.

waifu2x-js: https://github.com/takuyaa/waifu2x-js

Note
===

This software includes a binary and/or source version of model data from waifu2x
which can be obtained from https://github.com/nagadomi/waifu2x.

License
===

[MIT License](https://opensource.org/licenses/MIT)

126 changes: 126 additions & 0 deletions channel_image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package waifu2x

import (
"fmt"
"math"
)

type ChannelImage struct {
Width int
Height int
Buffer []uint8
}

func NewChannelImage(w, h int) *ChannelImage {
return &ChannelImage{
Width: w,
Height: h,
Buffer: make([]uint8, w*h), //XXX 0以下を0, 255以上を255 として登録する必要あり
}
}

func channelDecompose(pix []uint8, width, height int) (r, g, b, a *ChannelImage) {
r = NewChannelImage(width, height)
g = NewChannelImage(width, height)
b = NewChannelImage(width, height)
a = NewChannelImage(width, height)
for w := 0; w < width; w++ {
for h := 0; h < height; h++ {
i := w + h*width
r.Buffer[i] = pix[(w*4)+(h*width*4)]
g.Buffer[i] = pix[(w*4)+(h*width*4)+1]
b.Buffer[i] = pix[(w*4)+(h*width*4)+2]
a.Buffer[i] = pix[(w*4)+(h*width*4)+3]
}
}
return
}

func channelCompose(imageR, imageG, imageB, imageA *ChannelImage) (pix []uint8, width, height int) {
width = imageR.Width
height = imageR.Height
image := make([]uint8, width*height*4)
if width*height != len(imageR.Buffer) {
panic(fmt.Errorf("channelCompose() buflen:%d, width*height:%d", len(imageR.Buffer), width*height))
}
for i := 0; i < width*height; i++ {
image[i*4] = imageR.Buffer[i]
image[i*4+1] = imageG.Buffer[i]
image[i*4+2] = imageB.Buffer[i]
image[i*4+3] = 255
}
return image, width, height
}

func (c ChannelImage) extrapolation(px int) *ChannelImage {
width := c.Width
height := c.Height
toIndex := func(w, h int) int {
return w + h*width
}
imageEx := NewChannelImage(width+(2*px), height+(2*px))
for h := 0; h < height+(px*2); h++ {
for w := 0; w < width+(px*2); w++ {
index := w + h*(width+(px*2))
if w < px {
// Left outer area
if h < px {
// Left upper area
imageEx.Buffer[index] = c.Buffer[toIndex(0, 0)]
} else if px+height <= h {
// Left lower area
imageEx.Buffer[index] = c.Buffer[toIndex(0, height-1)]
} else {
// Left outer area
imageEx.Buffer[index] = c.Buffer[toIndex(0, h-px)]
}
} else if px+width <= w {
// Right outer area
if h < px {
// Right upper area
imageEx.Buffer[index] = c.Buffer[toIndex(width-1, 0)]
} else if px+height <= h {
// Right lower area
imageEx.Buffer[index] = c.Buffer[toIndex(width-1, height-1)]
} else {
// Right outer area
imageEx.Buffer[index] = c.Buffer[toIndex(width-1, h-px)]
}
} else if h < px {
// Upper outer area
imageEx.Buffer[index] = c.Buffer[toIndex(w-px, 0)]
} else if px+height <= h {
// Lower outer area
imageEx.Buffer[index] = c.Buffer[toIndex(w-px, height-1)]
} else {
// Inner area
imageEx.Buffer[index] = c.Buffer[toIndex(w-px, h-px)]
}
}
}
return imageEx
}

func (c ChannelImage) resize(scale float64) *ChannelImage {
width := c.Width
height := c.Height
scaledWidth := int(math.Floor(float64(width)*scale + 0.5)) //Round
scaledHeight := int(math.Floor(float64(height)*scale + 0.5)) //Round
scaledImage := NewChannelImage(scaledWidth, scaledHeight)
for w := 0; w < scaledWidth; w++ {
for h := 0; h < scaledHeight; h++ {
scaledIndex := w + (h * scaledWidth)
wOriginal := int(math.Floor((float64(w+1)/scale)+0.5) - 1) //Round
if wOriginal < 0 {
wOriginal = 0
}
hOriginal := int(math.Floor((float64(h+1)/scale)+0.5) - 1) //Round
if hOriginal < 0 {
hOriginal = 0
}
indexOriginal := wOriginal + (hOriginal * width)
scaledImage.Buffer[scaledIndex] = c.Buffer[indexOriginal]
}
}
return scaledImage
}
98 changes: 98 additions & 0 deletions cmd/waifu2x/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package main

import (
"bytes"
"fmt"
"image"
"image/png"
"io"
"os"

"github.com/ikawaha/waifu2x-go"
"github.com/ikawaha/waifu2x-go/data"
)

const (
scale2xModelFile = "models/anime_style_art_rgb/scale2.0x_model.json"
noiseModelFile = "models/anime_style_art_rgb/noise2_model.json"
)

func run(opt *option) error {
fp, err := os.Open(opt.input)
if err != nil {
return fmt.Errorf("input file %v, %v", opt.input, err)
}
defer fp.Close()
img, err := png.Decode(fp)
if err != nil {
return fmt.Errorf("load file %v, %v", opt.input, err)
}

var pix []uint8
switch t := img.(type) {
case *image.RGBA:
pix = t.Pix
case *image.NRGBA:
pix = t.Pix
default:
return fmt.Errorf("unknown image format, %T", t)
}

buf0, err := data.Asset(scale2xModelFile)
if err != nil {
return fmt.Errorf("open scale2x model, %v", err)
}
model2x, err := waifu2x.LoadModel(bytes.NewBuffer(buf0))
if err != nil {
return fmt.Errorf("load scale2x model, %v", err)
}

buf1, err := data.Asset(noiseModelFile)
if err != nil {
return fmt.Errorf("open noise model, %v", err)
}
noise, err := waifu2x.LoadModel(bytes.NewBuffer(buf1))
if err != nil {
return fmt.Errorf("load noise model, %v", err)
}

model := waifu2x.Waifu2x{
Scale2xModel: model2x,
NoiseModel: noise,
Scale: opt.scale,
IsDenoising: true,
}

pix, width, height := model.Calc(pix, img.Bounds().Max.X, img.Bounds().Max.Y)
rect0 := image.Rectangle{
Min: image.Point{X: 0, Y: 0},
Max: image.Point{X: width, Y: height},
}

switch t := img.(type) {
case *image.RGBA:
t.Pix = pix
t.Rect = rect0
t.Stride = rect0.Dx() * 4
case *image.NRGBA:
t.Pix = pix
t.Rect = rect0
t.Stride = rect0.Dx() * 4
default:
return fmt.Errorf("unknown image format, %T", t)
}

var w io.Writer = os.Stdout
if opt.output != "" {
fp, err := os.Create(opt.output)
if err != nil {
return fmt.Errorf("output file, %v", err)
}
defer fp.Close()
w = fp
}
if err := png.Encode(w, img); err != nil {
panic(err)
}
return nil
}
77 changes: 77 additions & 0 deletions cmd/waifu2x/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"flag"
"fmt"
"io"
"os"
)

const (
commandName = "waifu2x"
usageMessage = "%s -input <input_file> [-output <output_file>] [-scale <scale_factor>]\n"
)

type option struct {
scale float64
input string
output string
flagSet *flag.FlagSet
}

func newOption(w io.Writer, eh flag.ErrorHandling) (o *option) {
o = &option{
// ContinueOnError ErrorHandling // Return a descriptive error.
// ExitOnError // Call os.Exit(2).
// PanicOnError // Call panic with a descriptive error.flag.ContinueOnError
flagSet: flag.NewFlagSet(commandName, eh),
}
// option settings
o.flagSet.SetOutput(w)
o.flagSet.Float64Var(&o.scale, "scale", 2.0, "scale >= 1.0")
o.flagSet.StringVar(&o.input, "input", "", "input file")
o.flagSet.StringVar(&o.output, "output", "", "output file (default stdout)")

return
}

func (o *option) parse(args []string) (err error) {
if err = o.flagSet.Parse(args); err != nil {
return
}
// validations
if nonFlag := o.flagSet.Args(); len(nonFlag) != 0 {
return fmt.Errorf("invalid argument: %v", nonFlag)
}
if o.input == "" {
return fmt.Errorf("input file is empty\n")
}
if o.scale < 1.0 {
return fmt.Errorf("invalid scale, %v > 1", o.scale)
}
return
}

func Usage() {
fmt.Printf(usageMessage, commandName)
}

func PrintDefaults(eh flag.ErrorHandling) {
o := newOption(os.Stderr, eh)
o.flagSet.PrintDefaults()
}

func main() {
opt := newOption(os.Stderr, flag.ExitOnError)
if err := opt.parse(os.Args[1:]); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
Usage()
PrintDefaults(flag.ExitOnError)
os.Exit(1)
}
if err := run(opt); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
os.Exit(0)
}
Loading

0 comments on commit 4b9d88c

Please sign in to comment.