Skip to content

Instantly share code, notes, and snippets.

@eikeon
Last active December 10, 2015 05:58
Show Gist options
  • Save eikeon/4391196 to your computer and use it in GitHub Desktop.
Save eikeon/4391196 to your computer and use it in GitHub Desktop.
A server for generating and serving tiles from jp2 source image(s).
package main
// #cgo LDFLAGS: -lopenjp2
// #include <stdio.h>
// #include <openjpeg-2.0/openjpeg.h>
import "C"
import (
"errors"
"fmt"
"image"
"image/color"
"log"
"reflect"
"runtime"
"unsafe"
)
const MAX_PROGRESSION_LEVEL = uint(6)
func scaled_dimension(progression_level uint, dimension int) int {
scale_factor := uint(2) << (progression_level - uint(1))
return int(float32(dimension) / float32(scale_factor))
}
func desired_progression_level(r image.Rectangle, width, height int) uint {
level := MAX_PROGRESSION_LEVEL
for ; level > 1 && width > scaled_dimension(level, r.Dx()) && height > scaled_dimension(level, r.Dy()); level-- {
}
return level
}
func NewImageTile(filename string, r image.Rectangle, width, height int) (err error, tile *ImageTile) {
fsrc := C.fopen(C.CString(filename), C.CString("rb"))
if fsrc == nil {
return fmt.Errorf("failed to open '%s' for reading", filename), nil
}
defer func() {
if fsrc != nil {
C.fclose(fsrc)
}
}()
l_stream := C.opj_stream_create_default_file_stream(fsrc, 1)
if l_stream == nil {
return errors.New("failed to create stream"), nil
}
l_codec := C.opj_create_decompress(C.OPJ_CODEC_JP2)
var parameters C.opj_dparameters_t
C.opj_set_default_decoder_parameters(&parameters)
level := desired_progression_level(r, width, height)
log.Println("desired level:", level)
//(parameters).cp_reduce = C.OPJ_UINT32(level)
if err == nil && C.opj_setup_decoder(l_codec, &parameters) == C.OPJ_FALSE {
err = errors.New("failed to setup decoder")
}
if err == nil && C.opj_set_decoded_resolution_factor(l_codec, C.OPJ_UINT32(level)) == C.OPJ_FALSE {
err = errors.New("failed to set decode resolution factor")
}
var img *C.opj_image_t
if err == nil && C.opj_read_header(l_stream, l_codec, &img) == C.OPJ_FALSE {
err = errors.New("failed to read the header")
}
log.Println("num comps:", img.numcomps)
log.Println("x0:", img.x0, "x1:", img.x1, "y0:", img.y0, "y1:", img.y1)
if err == nil && C.opj_set_decode_area(l_codec, img, C.OPJ_INT32(r.Min.X), C.OPJ_INT32(r.Min.Y), C.OPJ_INT32(r.Max.X), C.OPJ_INT32(r.Max.Y)) == C.OPJ_FALSE {
err = errors.New("failed to set the decoded area")
}
if err == nil && C.opj_decode(l_codec, l_stream, img) == C.OPJ_FALSE {
err = errors.New("failed to decode image")
}
if err == nil && C.opj_end_decompress(l_codec, l_stream) == C.OPJ_FALSE {
err = errors.New("failed to decode image")
}
C.opj_stream_destroy(l_stream)
if l_codec != nil {
C.opj_destroy_codec(l_codec)
}
if err == nil {
var comps []C.opj_image_comp_t
compsSlice := (*reflect.SliceHeader)((unsafe.Pointer(&comps)))
compsSlice.Cap = int(img.numcomps)
compsSlice.Len = int(img.numcomps)
compsSlice.Data = uintptr(unsafe.Pointer(img.comps))
bounds := image.Rect(0, 0, int(comps[0].w), int(comps[0].h))
var data []int32
dataSlice := (*reflect.SliceHeader)((unsafe.Pointer(&data)))
dataSlice.Cap = bounds.Dx() * bounds.Dy()
dataSlice.Len = bounds.Dx() * bounds.Dy()
dataSlice.Data = uintptr(unsafe.Pointer(comps[0].data))
tile = &ImageTile{data, bounds, bounds.Dx(), img}
runtime.SetFinalizer(tile, func(it *ImageTile) {
C.opj_image_destroy(it.img)
})
} else {
C.opj_image_destroy(img)
}
return
}
type ImageTile struct {
data []int32
bounds image.Rectangle
stride int
img *C.opj_image_t
}
func (p *ImageTile) ColorModel() color.Model {
return color.GrayModel
}
func (p *ImageTile) Bounds() image.Rectangle {
return p.bounds
}
func (p *ImageTile) At(x, y int) color.Color {
if !(image.Point{x, y}.In(p.bounds)) {
return color.Gray{}
}
index := p.PixOffset(x, y)
return color.Gray{uint8(p.data[index])}
}
func (p *ImageTile) PixOffset(x, y int) int {
return (y-p.bounds.Min.Y)*p.stride + (x-p.bounds.Min.X)*1
}
package main
import (
"flag"
"image"
"image/jpeg"
"log"
"net/http"
"regexp"
"strconv"
)
var e = regexp.MustCompile(`/images/tiles/(?P<path>.+)/image_(?P<width>\d+)x(?P<height>\d+)_from_(?P<x1>\d+),(?P<y1>\d+)_to_(?P<x2>\d+),(?P<y2>\d+).jpg`)
func TileHandler(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "image/jpeg")
parts := e.FindStringSubmatch(req.URL.Path)
d := map[string]string{}
for i, name := range e.SubexpNames() {
d[name] = parts[i]
}
path := d["path"]
x1, _ := strconv.Atoi(d["x1"])
y1, _ := strconv.Atoi(d["y1"])
x2, _ := strconv.Atoi(d["x2"])
y2, _ := strconv.Atoi(d["y2"])
r := image.Rect(x1, y1, x2, y2)
width, _ := strconv.Atoi(d["width"])
height, _ := strconv.Atoi(d["height"])
if err, i := NewImageTile(path, r, width, height); err == nil {
if err = jpeg.Encode(w, i, nil); err != nil {
log.Println(err)
}
} else {
log.Println(err)
}
}
func main() {
Address := flag.String("address", ":8888", "http service address")
flag.Parse()
http.Handle("/", http.HandlerFunc(TileHandler))
if err := http.ListenAndServe(*Address, nil); err != nil {
log.Print("ListenAndServe:", err)
}
}
@eikeon
Copy link
Author

eikeon commented Feb 8, 2013

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment