<commit_before>package main

import (
	"errors"
	"github.com/uoregon-libraries/rais-image-server/iiif"
	"github.com/uoregon-libraries/rais-image-server/openjpeg"
	"github.com/uoregon-libraries/rais-image-server/transform"
	"image"
	"image/color"
	"image/draw"
	"log"
	"os"
	"path"
	"strings"
)

var (
	ErrImageDoesNotExist = errors.New("Image file does not exist")
	ErrInvalidFiletype   = errors.New("Invalid or unknown file type")
	ErrDecodeImage       = errors.New("Unable to decode image")
	ErrBadImageFile      = errors.New("Unable to read image")
)

// IIIFImage defines an interface for reading images in a generic way.  It's
// heavily biased toward the way we've had to do our JP2 images since they're
// the more unusual use-case.
type IIIFImage interface {
	DecodeImage() (image.Image, error)
	GetWidth() int
	GetHeight() int
	SetCrop(image.Rectangle)
	SetResizeWH(int, int)
	SetScale(float64)
}

type ImageResource struct {
	Image      IIIFImage
	ID         iiif.ID
	FilePath   string
}

// Initializes and returns an ImageResource for the given id and path.  If the path
// doesn't resolve to a valid file, or resolves to a file type that isn't
// supported, an error is returned.  File type is determined by extension, so
// images will need standard extensions in order to work.
func NewImageResource(id iiif.ID, filepath string) (*ImageResource, error) {
	var err error

	// First, does the file exist?
	if _, err = os.Stat(filepath); err != nil {
		log.Printf("Image does not exist: %#v", filepath)
		return nil, ErrImageDoesNotExist
	}

	// File exists - is it a valid filetype?
	var i IIIFImage
	fileExt := strings.ToLower(path.Ext(filepath))
	switch fileExt {
	case ".jp2":
		i, err = openjpeg.NewJP2Image(filepath)
	case ".tif", ".tiff", ".png", ".jpg", "jpeg", ".gif":
		i, err = NewSimpleImage(filepath)
	default:
		log.Printf("Image type unknown / invalid: %#v", filepath)
		return nil, ErrInvalidFiletype
	}

	if err != nil {
		log.Printf("Unable to read image %#v: %s", filepath)
		return nil, ErrBadImageFile
	}

	img := &ImageResource{ID: id, Image: i, FilePath: filepath}
	return img, nil
}

// Apply runs all image manipulation operations described by the IIIF URL, and
// returns an image.Image ready for encoding to the client
func (res *ImageResource) Apply(u *iiif.URL) (image.Image, error) {
	// Crop and resize have to be prepared before we can decode
	res.prep(u.Region, u.Size)

	img, err := res.Image.DecodeImage()
	if err != nil {
		log.Println("Unable to decode image: ", err)
		return nil, ErrDecodeImage
	}

	if u.Rotation.Degrees != 0 {
		img = rotate(img, u.Rotation)
	}

	// Unless I'm missing something, QColor doesn't actually change an image -
	// e.g., if it's already color, nothing happens.  If it's grayscale, there's
	// nothing to do (obviously we shouldn't report it, but oh well)
	switch u.Quality {
	case iiif.QGray:
		img = grayscale(img)
	case iiif.QBitonal:
		img = bitonal(img)
	}

	return img, nil
}

func (res *ImageResource) prep(r iiif.Region, s iiif.Size) {
	w, h := res.Image.GetWidth(), res.Image.GetHeight()
	var crop image.Rectangle

	switch r.Type {
	case iiif.RTPixel:<commit_msg>Fix crop var<commit_after>package main

import (
	"errors"
	"github.com/uoregon-libraries/rais-image-server/iiif"
	"github.com/uoregon-libraries/rais-image-server/openjpeg"
	"github.com/uoregon-libraries/rais-image-server/transform"
	"image"
	"image/color"
	"image/draw"
	"log"
	"os"
	"path"
	"strings"
)

var (
	ErrImageDoesNotExist = errors.New("Image file does not exist")
	ErrInvalidFiletype   = errors.New("Invalid or unknown file type")
	ErrDecodeImage       = errors.New("Unable to decode image")
	ErrBadImageFile      = errors.New("Unable to read image")
)

// IIIFImage defines an interface for reading images in a generic way.  It's
// heavily biased toward the way we've had to do our JP2 images since they're
// the more unusual use-case.
type IIIFImage interface {
	DecodeImage() (image.Image, error)
	GetWidth() int
	GetHeight() int
	SetCrop(image.Rectangle)
	SetResizeWH(int, int)
	SetScale(float64)
}

type ImageResource struct {
	Image      IIIFImage
	ID         iiif.ID
	FilePath   string
}

// Initializes and returns an ImageResource for the given id and path.  If the path
// doesn't resolve to a valid file, or resolves to a file type that isn't
// supported, an error is returned.  File type is determined by extension, so
// images will need standard extensions in order to work.
func NewImageResource(id iiif.ID, filepath string) (*ImageResource, error) {
	var err error

	// First, does the file exist?
	if _, err = os.Stat(filepath); err != nil {
		log.Printf("Image does not exist: %#v", filepath)
		return nil, ErrImageDoesNotExist
	}

	// File exists - is it a valid filetype?
	var i IIIFImage
	fileExt := strings.ToLower(path.Ext(filepath))
	switch fileExt {
	case ".jp2":
		i, err = openjpeg.NewJP2Image(filepath)
	case ".tif", ".tiff", ".png", ".jpg", "jpeg", ".gif":
		i, err = NewSimpleImage(filepath)
	default:
		log.Printf("Image type unknown / invalid: %#v", filepath)
		return nil, ErrInvalidFiletype
	}

	if err != nil {
		log.Printf("Unable to read image %#v: %s", filepath)
		return nil, ErrBadImageFile
	}

	img := &ImageResource{ID: id, Image: i, FilePath: filepath}
	return img, nil
}

// Apply runs all image manipulation operations described by the IIIF URL, and
// returns an image.Image ready for encoding to the client
func (res *ImageResource) Apply(u *iiif.URL) (image.Image, error) {
	// Crop and resize have to be prepared before we can decode
	res.prep(u.Region, u.Size)

	img, err := res.Image.DecodeImage()
	if err != nil {
		log.Println("Unable to decode image: ", err)
		return nil, ErrDecodeImage
	}

	if u.Rotation.Degrees != 0 {
		img = rotate(img, u.Rotation)
	}

	// Unless I'm missing something, QColor doesn't actually change an image -
	// e.g., if it's already color, nothing happens.  If it's grayscale, there's
	// nothing to do (obviously we shouldn't report it, but oh well)
	switch u.Quality {
	case iiif.QGray:
		img = grayscale(img)
	case iiif.QBitonal:
		img = bitonal(img)
	}

	return img, nil
}

func (res *ImageResource) prep(r iiif.Region, s iiif.Size) {
	w, h := res.Image.GetWidth(), res.Image.GetHeight()
	crop := image.Rect(0, 0, w, h)

	switch r.Type {
	case iiif.RTPixel:
{'fraction_non_alphanumeric': 0.1180852750039626, 'fraction_numerical': 0.0022190521477254716, 'mean_word_length': 3.7586726998491704, 'pattern_counts': {'":': 4, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2241629', 'n_tokens_mistral': 2221, 'n_tokens_neox': 2121, 'n_words': 711}