Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Weird behaviour when LayoutF returns small sizes, e.g. 1.33,1.00 #2988

Closed
1 of 11 tasks
mikijov opened this issue May 9, 2024 · 8 comments
Closed
1 of 11 tasks

Weird behaviour when LayoutF returns small sizes, e.g. 1.33,1.00 #2988

mikijov opened this issue May 9, 2024 · 8 comments

Comments

@mikijov
Copy link

mikijov commented May 9, 2024

Ebitengine Version

2.7.3

Operating System

  • Windows
  • macOS
  • Linux
  • FreeBSD
  • OpenBSD
  • Android
  • iOS
  • Nintendo Switch
  • PlayStation 5
  • Xbox
  • Web Browsers

Go Version (go version)

go version go1.22.2 linux/amd64

What steps will reproduce the problem?

Run this code, and provide your own image in the "resources/" directory.

// Package main...
package main

import (
	"embed"
	"image"
	_ "image/png"
	"log"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/hajimehoshi/ebiten/v2/inpututil"
)

//go:embed resources/*
var resources embed.FS

type game struct {
	outsideWidth  float64
	outsizeHeight float64
	width         float64
	height        float64
	m             *ebiten.Image

	x  float64
	y  float64
	dx float64
	dy float64
}

func (g *game) Layout(_, _ int) (int, int) {
	// As game implements the interface LayoutFer, Layout is never called and LayoutF is called
	// instead. However, game has to implement Layout to satisfy the interface Game.
	panic("windowsize: Layout must not be called")
}

func (g *game) LayoutF(outsideWidth, outsideHeight float64) (float64, float64) {
	// Target ratio is 1.33/1, basically aiming for 4/3 ratio, but keeping the screen unit size to
	// be 1. If the screen is wider, keep height at 1, and extend the width to whatever calculation
	// yields, e.g. 2.5/1. If the screen is taller on the other hand, then keep the height at 1.33
	// and make the height as small as required, e.g. 1.33/2.

	if outsideWidth == g.outsideWidth && outsideHeight == g.outsizeHeight {
		return g.width, g.height
	}

	g.outsideWidth = outsideWidth
	g.outsizeHeight = outsideHeight

	targetRatio := 4.0 / 3.0
	ratio := outsideWidth / outsideHeight

	if ratio > targetRatio {
		g.width = ratio
		g.height = 1
	} else {
		g.width = targetRatio
		g.height = outsideHeight * targetRatio / outsideWidth
	}

	log.Printf("Layout: %.2f:%.2f\n", g.width, g.height)

	return g.width, g.height
}

func (g *game) Update() error {
	if ebiten.IsKeyPressed(ebiten.KeyAlt) && inpututil.IsKeyJustPressed(ebiten.KeyEnter) {
		ebiten.SetFullscreen(!ebiten.IsFullscreen())
	}
	return nil
}

var ox float64
var oy float64

func (g *game) Draw(screen *ebiten.Image) {
	screen.Clear()

	imageSize := g.m.Bounds()

	var op ebiten.DrawImageOptions
	op.GeoM.Scale(1/float64(imageSize.Dx()), 1/float64(imageSize.Dy()))
	// op.GeoM.Scale(0.5, 0.5)
	tx := g.width/2 - 0.5
	ty := g.height/2 - 0.5
	if tx != ox || ty != oy {
		ox = tx
		oy = ty
		log.Printf("Translate: %.3fx%.3f\n", tx, ty)
	}
	op.GeoM.Translate(tx, ty)

	screen.DrawImage(g.m, &op)
}

func main() {
	file, err := resources.Open("resources/512-x-512-32-bit-png-with-alpha-11.png")
	if err != nil {
		log.Fatalf("Failed to open image file: %s\n", err)
	}
	m, format, err := image.Decode(file)
	if err != nil {
		log.Fatalf("Failed to open image file: %s\n", err)
	}
	log.Printf("Image type: %s\n", format)

	ebiten.SetWindowTitle("shooter")
	ebiten.SetVsyncEnabled(true)
	ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)

	options := &ebiten.RunGameOptions{
		X11ClassName:    "shooter-class-name",
		X11InstanceName: "shooter-instance-name",
	}

	game := &game{
		width:  4,
		height: 3,
		m:      ebiten.NewImageFromImage(m),
	}

	if err := ebiten.RunGameWithOptions(game, options); err != nil {
		log.Fatal(err)
	}
}

What is the expected result?

  1. The rectangle image should be centered.
  2. Image should stay centered when resizing the window.
  3. Image should show contents properly, and not just colored rectangle.

What happens instead?

Image stays in place until the window size crosses some boundary; then the image jumps to new place and stays in place until next "boundary".

Anything else you feel useful to add?

I am guessing, but it seems that internally engine is using integers, so using floats for drawing, sizing etc like I do hits some rounding issue internally. I.e. the engine cannot work with pure floats.

@mikijov mikijov added the bug label May 9, 2024
@hajimehoshi
Copy link
Owner

Would it be possible to minimize the reproducible case more? Thanks

@hajimehoshi hajimehoshi added this to the v2.8.0 milestone May 10, 2024
@hajimehoshi
Copy link
Owner

1.33,1.00

You mean the size less than 2...?

@mikijov
Copy link
Author

mikijov commented May 10, 2024

Yes. I wanted to make scalable UI/game, so that at any resolution I am drawing in 0..1 coords.

I will try to simplify the code tomorrow.

@hajimehoshi
Copy link
Owner

This means the screen size is only 1 or 2 pixels. Ebitengine doesn't expose the texels (0-1).

@mikijov
Copy link
Author

mikijov commented May 10, 2024

... well, then I am not understanding the documentation. LayoutF allows for float return size, and the docs say:
Even though the outside size and the screen size differ, the rendering scale is automatically adjusted to fit with the outside. I interpret this that whatever size I return from Layout will be scaled to physical window size. So, I don't see a difference between 1.33,1.00, or 133,100, or 133000,100000. All these should be scaled to the same physical window, and hence should produce the same image.

If there is expectation for Layout() to return size in pixels, then I do not understand LayoutF(). What would the meaning of e.g. return 640.445, 320.128 mean? Fractional pixel sizes?

@hajimehoshi
Copy link
Owner

hajimehoshi commented May 10, 2024

There is expectation both for Layout and LayoutF to return size in pixels. Returning 640.445, 320.128 at LayoutF means the internal offscreen framebuffer size is (probably) 641 and 321, and 640.445, 320.128 are also used for the scale of the offscreen framebuffer to the final screen framebuffer.

@mikijov
Copy link
Author

mikijov commented May 10, 2024

Fair enough. I still do not understand your explanation for fractional pixel sizes returned from LayoutF(). But I don't have to understand it. I would just kindly ask to add a comment to Layout() that the return should be in pixels. That would make it obvous that e.g. 1.33 would show only a single pixel. The issue can be closed if you agree.

@hajimehoshi
Copy link
Owner

Sure, I'll add more comments. Thanks!

@hajimehoshi hajimehoshi closed this as not planned Won't fix, can't repro, duplicate, stale May 10, 2024
@hajimehoshi hajimehoshi removed this from the v2.8.0 milestone May 10, 2024
hajimehoshi added a commit that referenced this issue May 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants