mirror of
https://github.com/Dadido3/noita-mapcap.git
synced 2024-11-18 17:17:31 +00:00
157 lines
4.7 KiB
Go
157 lines
4.7 KiB
Go
// Copyright (c) 2019-2022 David Vogel
|
|
//
|
|
// This software is released under the MIT License.
|
|
// https://opensource.org/licenses/MIT
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
_ "image/png"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/nfnt/resize"
|
|
"github.com/tdewolff/canvas"
|
|
"github.com/tdewolff/canvas/renderers/rasterizer"
|
|
)
|
|
|
|
type imageTile struct {
|
|
fileName string
|
|
|
|
scaleDivider int // Downscales the coordinates and images on the fly.
|
|
|
|
offset image.Point // Correction offset of the image, so that it aligns pixel perfect with other images. Determined by image matching.
|
|
|
|
image image.Image // Either a rectangle or an RGBA image. The bounds of this image are determined by the filename.
|
|
imageMutex *sync.RWMutex //
|
|
imageUsedFlag bool // Flag signalling, that the image was used recently.
|
|
|
|
pixelErrorSum uint64 // Sum of the difference between the (sub)pixels of all overlapping images. 0 Means that all overlapping images are identical.
|
|
|
|
entities []Entity // List of entities that may lie on or near this image tile.
|
|
playerPath *PlayerPath // Contains the player path.
|
|
}
|
|
|
|
func (it *imageTile) GetImage() (*image.RGBA, error) {
|
|
it.imageMutex.RLock()
|
|
|
|
it.imageUsedFlag = true // Race condition may happen on this flag, but doesn't matter here.
|
|
|
|
// Check if the image is already loaded.
|
|
if img, ok := it.image.(*image.RGBA); ok {
|
|
it.imageMutex.RUnlock()
|
|
return img, nil
|
|
}
|
|
|
|
it.imageMutex.RUnlock()
|
|
// It's possible that the image got changed in between here.
|
|
it.imageMutex.Lock()
|
|
defer it.imageMutex.Unlock()
|
|
|
|
// Check again if the image is already loaded.
|
|
if img, ok := it.image.(*image.RGBA); ok {
|
|
return img, nil
|
|
}
|
|
|
|
// Store rectangle of the old image.
|
|
oldRect := it.image.Bounds()
|
|
|
|
file, err := os.Open(it.fileName)
|
|
if err != nil {
|
|
return &image.RGBA{}, err
|
|
}
|
|
defer file.Close()
|
|
|
|
img, _, err := image.Decode(file)
|
|
if err != nil {
|
|
return &image.RGBA{}, err
|
|
}
|
|
|
|
if it.scaleDivider > 1 {
|
|
img = resize.Resize(uint(oldRect.Dx()), uint(oldRect.Dy()), img, resize.NearestNeighbor)
|
|
}
|
|
|
|
imgRGBA, ok := img.(*image.RGBA)
|
|
if !ok {
|
|
return &image.RGBA{}, fmt.Errorf("expected an RGBA image, got %T instead", img)
|
|
}
|
|
|
|
scaledRect := imgRGBA.Rect.Add(oldRect.Min)
|
|
|
|
// Draw entities.
|
|
// tdewolff/canvas doesn't respect the image boundaries, so we have to draw on the image before we move its rectangle.
|
|
if len(it.entities) > 0 {
|
|
c := canvas.New(float64(imgRGBA.Rect.Dx()), float64(imgRGBA.Rect.Dy()))
|
|
ctx := canvas.NewContext(c)
|
|
ctx.SetCoordSystem(canvas.CartesianIV)
|
|
ctx.SetCoordRect(canvas.Rect{X: -float64(oldRect.Min.X), Y: -float64(oldRect.Min.Y), W: float64(imgRGBA.Rect.Dx()), H: float64(imgRGBA.Rect.Dy())}, float64(imgRGBA.Rect.Dx()), float64(imgRGBA.Rect.Dy()))
|
|
for _, entity := range it.entities {
|
|
// Check if entity origin is near or around the current image rectangle.
|
|
entityOrigin := image.Point{int(entity.Transform.X), int(entity.Transform.Y)}
|
|
if entityOrigin.In(scaledRect.Inset(-512)) {
|
|
entity.Draw(ctx)
|
|
}
|
|
}
|
|
|
|
// Theoretically we would need to linearize imgRGBA first, but DefaultColorSpace assumes that the color space is linear already.
|
|
r := rasterizer.FromImage(imgRGBA, canvas.DPMM(1.0), canvas.DefaultColorSpace)
|
|
c.Render(r)
|
|
r.Close() // This just transforms the image's luminance curve back from linear into non linear.
|
|
}
|
|
|
|
// Draw player path.
|
|
if it.playerPath != nil {
|
|
c := canvas.New(float64(imgRGBA.Rect.Dx()), float64(imgRGBA.Rect.Dy()))
|
|
ctx := canvas.NewContext(c)
|
|
ctx.SetCoordSystem(canvas.CartesianIV)
|
|
ctx.SetCoordRect(canvas.Rect{X: -float64(oldRect.Min.X), Y: -float64(oldRect.Min.Y), W: float64(imgRGBA.Rect.Dx()), H: float64(imgRGBA.Rect.Dy())}, float64(imgRGBA.Rect.Dx()), float64(imgRGBA.Rect.Dy()))
|
|
|
|
it.playerPath.Draw(ctx, scaledRect)
|
|
|
|
// Theoretically we would need to linearize imgRGBA first, but DefaultColorSpace assumes that the color space is linear already.
|
|
r := rasterizer.FromImage(imgRGBA, canvas.DPMM(1.0), canvas.DefaultColorSpace)
|
|
c.Render(r)
|
|
r.Close() // This just transforms the image's luminance curve back from linear into non linear.
|
|
}
|
|
|
|
// Restore the position of the image rectangle.
|
|
imgRGBA.Rect = scaledRect
|
|
|
|
it.image = imgRGBA
|
|
|
|
// Free the image after some time.
|
|
go func() {
|
|
for it.imageUsedFlag {
|
|
it.imageUsedFlag = false
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
|
|
it.imageMutex.Lock()
|
|
defer it.imageMutex.Unlock()
|
|
it.image = it.image.Bounds()
|
|
}()
|
|
|
|
return imgRGBA, nil
|
|
}
|
|
|
|
func (it *imageTile) OffsetBounds() image.Rectangle {
|
|
it.imageMutex.RLock()
|
|
defer it.imageMutex.RUnlock()
|
|
|
|
return it.image.Bounds().Add(it.offset)
|
|
}
|
|
|
|
func (it *imageTile) Bounds() image.Rectangle {
|
|
it.imageMutex.RLock()
|
|
defer it.imageMutex.RUnlock()
|
|
|
|
return it.image.Bounds()
|
|
}
|
|
|
|
func (it *imageTile) String() string {
|
|
return fmt.Sprintf("<ImageTile \"%v\">", it.fileName)
|
|
}
|