<commit_before>// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ui

import (
	"fmt"

	"github.com/hajimehoshi/ebiten/v2/internal/atlas"
	"github.com/hajimehoshi/ebiten/v2/internal/graphics"
	"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
	"github.com/hajimehoshi/ebiten/v2/internal/mipmap"
)

// panicOnErrorOnReadingPixels indicates whether reading pixels panics on an error or not.
// This value is set only on testing.
var panicOnErrorOnReadingPixels bool

func SetPanicOnErrorOnReadingPixelsForTesting(value bool) {
	panicOnErrorOnReadingPixels = value
}

const bigOffscreenScale = 2

type Image struct {
	mipmap    *mipmap.Mipmap
	width     int
	height    int
	imageType atlas.ImageType

	dotsBuffer map[[2]int][4]byte

	// bigOffscreenBuffer is a double-sized offscreen for anti-alias rendering.
	bigOffscreenBuffer      *Image
	bigOffscreenBufferBlend graphicsdriver.Blend
	bigOffscreenBufferDirty bool
}

func NewImage(width, height int, imageType atlas.ImageType) *Image {
	return &Image{
		mipmap:    mipmap.New(width, height, imageType),
		width:     width,
		height:    height,
		imageType: imageType,
	}
}

func (i *Image) MarkDisposed() {
	if i.mipmap == nil {
		return
	}
	if i.bigOffscreenBuffer != nil {
		i.bigOffscreenBuffer.MarkDisposed()
		i.bigOffscreenBuffer = nil
		i.bigOffscreenBufferDirty = false
	}
	i.mipmap.MarkDisposed()
	i.mipmap = nil
	i.dotsBuffer = nil
}

func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint16, blend graphicsdriver.Blend, dstRegion, srcRegion graphicsdriver.Region, subimageOffsets [graphics.ShaderImageCount - 1][2]float32, shader *Shader, uniforms [][]float32, evenOdd bool, canSkipMipmap bool, antialias bool) {
	if antialias {
		// Flush the other buffer to make the buffers exclusive.
		i.flushDotsBufferIfNeeded()

		if i.bigOffscreenBufferBlend != blend {
			i.flushBigOffscreenBufferIfNeeded()
		}

		if i.bigOffscreenBuffer == nil {
			var imageType atlas.ImageType
			switch i.imageType {
			case atlas.ImageTypeRegular, atlas.ImageTypeUnmanaged:
				imageType = atlas.ImageTypeUnmanaged
			case atlas.ImageTypeScreen, atlas.ImageTypeVolatile:
				imageType = atlas.ImageTypeVolatile
			default:
				panic(fmt.Sprintf("ui: unexpected image type: %d", imageType))
			}
			i.bigOffscreenBuffer = NewImage(i.width*bigOffscreenScale, i.height*bigOffscreenScale, imageType)
		}

		i.bigOffscreenBufferBlend = blend

		// Copy the current rendering result to get the correct blending result.
		if blend != graphicsdriver.BlendSourceOver && !i.bigOffscreenBufferDirty {
			srcs := [graphics.ShaderImageCount]*Image{i}
			vs := graphics.QuadVertices(
				0, 0, float32(i.width), float32(i.height),
				bigOffscreenScale, 0, 0, bigOffscreenScale, 0, 0,
				1, 1, 1, 1)
			is := graphics.QuadIndices()
			dstRegion := graphicsdriver.Region{
				X:      0,
				Y:      0,
				Width:  float32(i.width * bigOffscreenScale),
				Height: float32(i.height * bigOffscreenScale),
			}
			i.bigOffscreenBuffer.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dstRegion, graphicsdriver.Region{}, [graphics.ShaderImageCount - 1][2]float32{}, NearestFilterShader, nil, false, true, false)
		}

		for i := 0; i < len(vertices); i += graphics.VertexFloatCount {
			vertices[i] *= bigOffscreenScale
			vertices[i+1] *= bigOffscreenScale
		}

		dstRegion.X *= bigOffscreenScale
		dstRegion.Y *= bigOffscreenScale
		dstRegion.Width *= bigOffscreenScale
		dstRegion.Height *= bigOffscreenScale

		i.bigOffscreenBuffer.DrawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegion, subimageOffsets, shader, uniforms, evenOdd, canSkipMipmap, false)
		i.bigOffscreenBufferDirty = true
		return
	}

	i.flushBufferIfNeeded()

	var srcMipmaps [graphics.ShaderImageCount]*mipmap.Mipmap
	for i, src := range srcs {
		if src == nil {
			continue
		}
		src.flushBufferIfNeeded()
		srcMipmaps[i] = src.mipmap
	}

	i.mipmap.DrawTriangles(srcMipmaps, vertices, indices, blend, dstRegion, srcRegion, subimageOffsets, shader.shader, uniforms, evenOdd, canSkipMipmap)
}

func (i *Image) WritePixels(pix []byte, x, y, width, height int) {
	if width == 1 && height == 1 {
		// Flush the other buffer to make the buffers exclusive.
		i.flushBigOffscreenBufferIfNeeded()

		if i.dotsBuffer == nil {
			i.dotsBuffer = map[[2]int][4]byte{}
		}

		var clr [4]byte
		copy(clr[:], pix)
		i.dotsBuffer[[2]int{x, y}] = clr

		// One square requires 6 indices (= 2 triangles).
		if len(i.dotsBuffer) >= graphics.IndicesCount/6 {
			i.flushDotsBufferIfNeeded()
		}
		return
	}

	i.flushBufferIfNeeded()
	i.mipmap.WritePixels(pix, x, y, width, height)
}

func (i *Image) ReadPixels(pixels []byte, x, y, width, height int) {
	// Check the error existence and avoid unnecessary calls.
	if theGlobalState.error() != nil {
		return
	}

	i.flushBigOffscreenBufferIfNeeded()

	if width == 1 && height == 1 {
		if c, ok := i.dotsBuffer[[2]int{x, y}]; ok {
			copy(pixels, c[:])
			return
		}
		// Do not call flushDotsBufferIfNeeded here. This would slow (image/draw).Draw.
		// See ebiten.TestImageDrawOver.
	} else {
		i.flushDotsBufferIfNeeded()
	}

	if err := theUI.readPixels(i.mipmap, pixels, x, y, width, height); err != nil {
		if panicOnErrorOnReadingPixels {
			panic(err)
		}
		theGlobalState.setError(err)
	}
}

func (i *Image) DumpScreenshot(name string, blackbg bool) (string, error) {
	return theUI.dumpScreenshot(i.mipmap, name, blackbg)
}

func (i *Image) flushBufferIfNeeded() {
	// The buffers are exclusive and the order should not matter.
	i.flushDotsBufferIfNeeded()
	i.flushBigOffscreenBufferIfNeeded()
}

func (i *Image) flushDotsBufferIfNeeded() {
	if len(i.dotsBuffer) == 0 {
		return
	}<commit_msg>internal/ui: bug fix: screenshot didn't include antialias renderings<commit_after>// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ui

import (
	"fmt"

	"github.com/hajimehoshi/ebiten/v2/internal/atlas"
	"github.com/hajimehoshi/ebiten/v2/internal/graphics"
	"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
	"github.com/hajimehoshi/ebiten/v2/internal/mipmap"
)

// panicOnErrorOnReadingPixels indicates whether reading pixels panics on an error or not.
// This value is set only on testing.
var panicOnErrorOnReadingPixels bool

func SetPanicOnErrorOnReadingPixelsForTesting(value bool) {
	panicOnErrorOnReadingPixels = value
}

const bigOffscreenScale = 2

type Image struct {
	mipmap    *mipmap.Mipmap
	width     int
	height    int
	imageType atlas.ImageType

	dotsBuffer map[[2]int][4]byte

	// bigOffscreenBuffer is a double-sized offscreen for anti-alias rendering.
	bigOffscreenBuffer      *Image
	bigOffscreenBufferBlend graphicsdriver.Blend
	bigOffscreenBufferDirty bool
}

func NewImage(width, height int, imageType atlas.ImageType) *Image {
	return &Image{
		mipmap:    mipmap.New(width, height, imageType),
		width:     width,
		height:    height,
		imageType: imageType,
	}
}

func (i *Image) MarkDisposed() {
	if i.mipmap == nil {
		return
	}
	if i.bigOffscreenBuffer != nil {
		i.bigOffscreenBuffer.MarkDisposed()
		i.bigOffscreenBuffer = nil
		i.bigOffscreenBufferDirty = false
	}
	i.mipmap.MarkDisposed()
	i.mipmap = nil
	i.dotsBuffer = nil
}

func (i *Image) DrawTriangles(srcs [graphics.ShaderImageCount]*Image, vertices []float32, indices []uint16, blend graphicsdriver.Blend, dstRegion, srcRegion graphicsdriver.Region, subimageOffsets [graphics.ShaderImageCount - 1][2]float32, shader *Shader, uniforms [][]float32, evenOdd bool, canSkipMipmap bool, antialias bool) {
	if antialias {
		// Flush the other buffer to make the buffers exclusive.
		i.flushDotsBufferIfNeeded()

		if i.bigOffscreenBufferBlend != blend {
			i.flushBigOffscreenBufferIfNeeded()
		}

		if i.bigOffscreenBuffer == nil {
			var imageType atlas.ImageType
			switch i.imageType {
			case atlas.ImageTypeRegular, atlas.ImageTypeUnmanaged:
				imageType = atlas.ImageTypeUnmanaged
			case atlas.ImageTypeScreen, atlas.ImageTypeVolatile:
				imageType = atlas.ImageTypeVolatile
			default:
				panic(fmt.Sprintf("ui: unexpected image type: %d", imageType))
			}
			i.bigOffscreenBuffer = NewImage(i.width*bigOffscreenScale, i.height*bigOffscreenScale, imageType)
		}

		i.bigOffscreenBufferBlend = blend

		// Copy the current rendering result to get the correct blending result.
		if blend != graphicsdriver.BlendSourceOver && !i.bigOffscreenBufferDirty {
			srcs := [graphics.ShaderImageCount]*Image{i}
			vs := graphics.QuadVertices(
				0, 0, float32(i.width), float32(i.height),
				bigOffscreenScale, 0, 0, bigOffscreenScale, 0, 0,
				1, 1, 1, 1)
			is := graphics.QuadIndices()
			dstRegion := graphicsdriver.Region{
				X:      0,
				Y:      0,
				Width:  float32(i.width * bigOffscreenScale),
				Height: float32(i.height * bigOffscreenScale),
			}
			i.bigOffscreenBuffer.DrawTriangles(srcs, vs, is, graphicsdriver.BlendCopy, dstRegion, graphicsdriver.Region{}, [graphics.ShaderImageCount - 1][2]float32{}, NearestFilterShader, nil, false, true, false)
		}

		for i := 0; i < len(vertices); i += graphics.VertexFloatCount {
			vertices[i] *= bigOffscreenScale
			vertices[i+1] *= bigOffscreenScale
		}

		dstRegion.X *= bigOffscreenScale
		dstRegion.Y *= bigOffscreenScale
		dstRegion.Width *= bigOffscreenScale
		dstRegion.Height *= bigOffscreenScale

		i.bigOffscreenBuffer.DrawTriangles(srcs, vertices, indices, blend, dstRegion, srcRegion, subimageOffsets, shader, uniforms, evenOdd, canSkipMipmap, false)
		i.bigOffscreenBufferDirty = true
		return
	}

	i.flushBufferIfNeeded()

	var srcMipmaps [graphics.ShaderImageCount]*mipmap.Mipmap
	for i, src := range srcs {
		if src == nil {
			continue
		}
		src.flushBufferIfNeeded()
		srcMipmaps[i] = src.mipmap
	}

	i.mipmap.DrawTriangles(srcMipmaps, vertices, indices, blend, dstRegion, srcRegion, subimageOffsets, shader.shader, uniforms, evenOdd, canSkipMipmap)
}

func (i *Image) WritePixels(pix []byte, x, y, width, height int) {
	if width == 1 && height == 1 {
		// Flush the other buffer to make the buffers exclusive.
		i.flushBigOffscreenBufferIfNeeded()

		if i.dotsBuffer == nil {
			i.dotsBuffer = map[[2]int][4]byte{}
		}

		var clr [4]byte
		copy(clr[:], pix)
		i.dotsBuffer[[2]int{x, y}] = clr

		// One square requires 6 indices (= 2 triangles).
		if len(i.dotsBuffer) >= graphics.IndicesCount/6 {
			i.flushDotsBufferIfNeeded()
		}
		return
	}

	i.flushBufferIfNeeded()
	i.mipmap.WritePixels(pix, x, y, width, height)
}

func (i *Image) ReadPixels(pixels []byte, x, y, width, height int) {
	// Check the error existence and avoid unnecessary calls.
	if theGlobalState.error() != nil {
		return
	}

	i.flushBigOffscreenBufferIfNeeded()

	if width == 1 && height == 1 {
		if c, ok := i.dotsBuffer[[2]int{x, y}]; ok {
			copy(pixels, c[:])
			return
		}
		// Do not call flushDotsBufferIfNeeded here. This would slow (image/draw).Draw.
		// See ebiten.TestImageDrawOver.
	} else {
		i.flushDotsBufferIfNeeded()
	}

	if err := theUI.readPixels(i.mipmap, pixels, x, y, width, height); err != nil {
		if panicOnErrorOnReadingPixels {
			panic(err)
		}
		theGlobalState.setError(err)
	}
}

func (i *Image) DumpScreenshot(name string, blackbg bool) (string, error) {
	i.flushBufferIfNeeded()
	return theUI.dumpScreenshot(i.mipmap, name, blackbg)
}

func (i *Image) flushBufferIfNeeded() {
	// The buffers are exclusive and the order should not matter.
	i.flushDotsBufferIfNeeded()
	i.flushBigOffscreenBufferIfNeeded()
}

func (i *Image) flushDotsBufferIfNeeded() {
	if len(i.dotsBuffer) == 0 {
		return
	}
{'fraction_non_alphanumeric': 0.11021932612841703, 'fraction_numerical': 0.010171646535282899, 'mean_word_length': 4.196118909991743, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '5272048', 'n_tokens_mistral': 4638, 'n_tokens_neox': 4248, 'n_words': 1088}