Skip to content

Commit

Permalink
image fit
Browse files Browse the repository at this point in the history
  • Loading branch information
Hobbyshop committed May 11, 2024
1 parent 9339a98 commit 8c24bc3
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 42 deletions.
26 changes: 14 additions & 12 deletions src/main/kotlin/com/neptuneclient/voidui/rendering/Renderer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,16 @@ interface Renderer {
* @param width The width of the image.
* @param height The height of the image.
* @param image An image object which contains data about the image.
* @param imageOffset The offset at which the image will be placed relative to [x] and [y].
*/
fun image(x: Float, y: Float, width: Float, height: Float, image: ImageBuffer)
fun image(x: Float, y: Float, width: Float, height: Float, image: ImageBuffer, imageOffset: Offset = Offset.zero)

fun image(x: Int, y: Int, width: Int, height: Int, image: ImageBuffer) {
image(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), image)
fun image(x: Int, y: Int, width: Int, height: Int, image: ImageBuffer, imageOffset: Offset = Offset.zero) {
image(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), image, imageOffset)
}

fun image(offset: Offset, size: Size, image: ImageBuffer) {
image(offset.x, offset.y, size.width, size.height, image)
fun image(offset: Offset, size: Size, image: ImageBuffer, imageOffset: Offset = Offset.zero) {
image(offset.x, offset.y, size.width, size.height, image, imageOffset)
}

/**
Expand All @@ -241,19 +242,20 @@ interface Renderer {
* @param height The height of the image.
* @param radius The radius of the image's corners.
* @param image An image object which contains data about the image.
* @param imageOffset The offset at which the image will be placed relative to [x] and [y].
*/
fun roundedImage(x: Float, y: Float, width: Float, height: Float, radius: CornerRadius, image: ImageBuffer)
fun roundedImage(x: Float, y: Float, width: Float, height: Float, radius: CornerRadius, image: ImageBuffer, imageOffset: Offset = Offset.zero)

fun roundedImage(x: Int, y: Int, width: Int, height: Int, radius: CornerRadius, image: ImageBuffer) {
roundedImage(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), radius, image)
fun roundedImage(x: Int, y: Int, width: Int, height: Int, radius: CornerRadius, image: ImageBuffer, imageOffset: Offset = Offset.zero) {
roundedImage(x.toFloat(), y.toFloat(), width.toFloat(), height.toFloat(), radius, image, imageOffset)
}

fun roundedImage(offset: Offset, size: Size, radius: CornerRadius, image: ImageBuffer) {
roundedImage(offset.x, offset.y, size.width, size.height, radius, image)
fun roundedImage(offset: Offset, size: Size, radius: CornerRadius, image: ImageBuffer, imageOffset: Offset = Offset.zero) {
roundedImage(offset.x, offset.y, size.width, size.height, radius, image, imageOffset)
}

fun roundedImage(x: Float, y: Float, width: Float, height: Float, radius: Float, image: ImageBuffer) {
roundedImage(x, y, width, height, CornerRadius.all(radius), image)
fun roundedImage(x: Float, y: Float, width: Float, height: Float, radius: Float, image: ImageBuffer, imageOffset: Offset = Offset.zero) {
roundedImage(x, y, width, height, CornerRadius.all(radius), image, imageOffset)
}

fun roundedImage(x: Int, y: Int, width: Int, height: Int, radius: Int, image: ImageBuffer) {
Expand Down
13 changes: 13 additions & 0 deletions src/main/kotlin/com/neptuneclient/voidui/utils/ImageBuffer.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.neptuneclient.voidui.utils

import com.neptuneclient.voidui.VoidUI
import com.neptuneclient.voidui.framework.Size
import com.neptuneclient.voidui.rendering.Renderer
import java.io.IOException
import java.nio.file.Path
import javax.imageio.ImageIO

/**
* Holds values which define an image in the library.
Expand All @@ -15,6 +17,11 @@ data class ImageBuffer(val path: Path) {
*/
var id: Int? = null

/**
* The size of the image in pixels.
*/
lateinit var size: Size

/**
* Registers an image to the renderer.
*
Expand All @@ -24,6 +31,12 @@ data class ImageBuffer(val path: Path) {
try {
val data = getBufferData(path)
id = renderer.registerImage(path, data)

val stream = VoidUI::class.java.classLoader.getResourceAsStream(path.toString()) ?:
throw IllegalArgumentException("Image buffer could not be loaded: $path")

val img = ImageIO.read(stream)
size = Size(img.width.toFloat(), img.height.toFloat())
} catch (e: IOException) {
VoidUI.LOGGER.error("Failed to register image $path", e)
}
Expand Down
98 changes: 92 additions & 6 deletions src/main/kotlin/com/neptuneclient/voidui/widgets/Image.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,22 @@ import com.neptuneclient.voidui.utils.ImageBuffer
* @param src The source of the image.
* @param imageSize A custom size for the image, if this is not set, the image will cover the entire parent widget.
* @param cornerRadius A custom corner radius for the image, if this is not set, the default value from the theme will be used.
* @param fit Sizes the image in relation to the parents constraints if [imageSize] is not set.
*/
class Image(
private val src: ImageBuffer,
private val imageSize: Size? = null,
private val cornerRadius: CornerRadius? = null
private val cornerRadius: CornerRadius? = null,
private val fit: ImageFit = ImageFit.KEEP
) : LeafWidget() {

private lateinit var radius: CornerRadius

/**
* The position of the image relative to the position of the widget.
*/
private var imageOffset = Offset.zero

override fun init(screen: Screen, parent: Widget) {
super.init(screen, parent)

Expand All @@ -29,11 +36,57 @@ class Image(
}

override fun layout(constraints: BoxConstraints) {
size = constraints.constrain(imageSize ?: Size(constraints.maxWidth, constraints.maxHeight))
if (imageSize != null) {
size = constraints.constrain(imageSize)
return
}

if (fit == ImageFit.KEEP) {
size = constraints.constrain(src.size)
return
}
size = Size(constraints.maxWidth, constraints.maxHeight)

src.size = when (fit) {
ImageFit.STRETCH -> Size(constraints.maxWidth, constraints.maxHeight)
ImageFit.COVER -> {
if (src.size.width > src.size.height) {
val ratio = src.size.height / constraints.maxHeight
Size(src.size.width / ratio, src.size.height / ratio)
} else {
val ratio = src.size.width / constraints.maxWidth
Size(src.size.width / ratio, src.size.height / ratio)
}
}
// TODO fix the issue with this some time
ImageFit.CONTAIN -> {
if (src.size.width > src.size.height) {
val ratio = src.size.width / constraints.maxWidth
Size(src.size.width / ratio, src.size.height / ratio)
} else {
val ratio = src.size.height / constraints.maxHeight
Size(src.size.width / ratio, src.size.height / ratio)
}
}
ImageFit.KEEP -> src.size
}
}

override fun postLayoutInit(parentOffset: Offset, parent: Widget) {
if (fit == ImageFit.COVER) {
imageOffset = if (src.size.width > src.size.height) {
val overflow = src.size.width - size.width
Offset(-overflow / 2f, 0f)
} else {
val overflow = src.size.height - size.height
Offset(0f, -overflow / 2f)
}
}
super.postLayoutInit(parentOffset, parent)
}

override fun createRenderObject(): RenderObject? {
return ImageRenderObject(offset, size, src, radius)
return ImageRenderObject(offset, size, src, radius, imageOffset)
}

override fun remove() {
Expand All @@ -42,11 +95,44 @@ class Image(
}
}

private class ImageRenderObject(offset: Offset, size: Size, private val image: ImageBuffer, private val radius: CornerRadius) : RenderObject(offset, size) {
private class ImageRenderObject(
offset: Offset,
size: Size,
private val image: ImageBuffer,
private val radius: CornerRadius,
private val imageOffset: Offset
) : RenderObject(offset, size) {

override fun render(renderer: Renderer) {
if (!radius.isEmpty())
renderer.roundedImage(offset, size, radius, image)
renderer.roundedImage(offset, size, radius, image, imageOffset)
else
renderer.image(offset, size, image)
renderer.image(offset, size, image, imageOffset)
}

}

/**
* Describes how the image should be sized in relation to the parent's constraints.
*/
enum class ImageFit {
/**
* The image will keep its original size in pixels.
*/
KEEP,

/**
* The image will be stretched to fit within the constraints perfectly.
*/
STRETCH,

/**
* The image will keep its ratio but will be scaled to cover the entire constraints.
*/
COVER,

/**
* The image will keep its ratio but will be sized to not overflow the constraints.
*/
CONTAIN
}
52 changes: 32 additions & 20 deletions src/test/kotlin/com/neptuneclient/voidui/tests/ScreenTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,39 @@ fun EpicButton(label: String): Widget {
)
}

fun Menu(): Widget {
return Container(
color = Color(14, 15, 20),
cornerRadius = CornerRadius.all(10f),
padding = EdgeInsets.all(20f),

child = Column(
gap = 20f,
crossAxisAlignment = CrossAxisAlignment.STRETCH,
children = arrayOf(
Container(
color = Color.RED,
child = Text(
label = "Neptune popo main menu",
align = TextAlign.CENTER
)
),
EpicButton("Singleplayer"),
EpicButton("Multiplayer")
)
)
)
}

class TestScreen(voidUI: VoidUI) : Screen(voidUI) {

override fun build(): Widget {
return Container(
color = Color(14, 15, 20),
cornerRadius = CornerRadius.all(10f),
padding = EdgeInsets.all(20f),

child = Column(
gap = 20f,
crossAxisAlignment = CrossAxisAlignment.STRETCH,
children = arrayOf(
Container(
color = Color.RED,
child = Text(
label = "Neptune popo main menu",
align = TextAlign.CENTER
)
),
EpicButton("Singleplayer"),
EpicButton("Multiplayer")
)
margin = EdgeInsets.all(10f),
child = Image(
src = image("images/hampter.png"),
fit = ImageFit.COVER,
cornerRadius = CornerRadius.all(10f)
)
)
}
Expand All @@ -66,13 +77,14 @@ private val template = Template { slot ->
Stack(
children = arrayOf(
Image(
src = image("images/hampter.png")
src = image("images/hampter2.png"),
fit = ImageFit.STRETCH
),
Center(slot)
)
)
}
val voidUI = VoidUI(TestRenderer(), TestTheme(), Settings(centeredScreen = false), template)
val voidUI = VoidUI(TestRenderer(), TestTheme(), Settings(centeredScreen = false), /*template*/)

fun main() {
val screen = TestScreen(voidUI)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,11 @@ class TestRenderer : Renderer {
}
}

override fun image(x: Float, y: Float, width: Float, height: Float, image: ImageBuffer) {
override fun image(x: Float, y: Float, width: Float, height: Float, image: ImageBuffer, imageOffset: Offset) {
if (image.id == null)
throw IllegalStateException("Image was not registered properly!")

val paint = NanoVG.nvgImagePattern(vg, x, y, width, height, 0f, image.id!!, 1.0F, NVGPaint.calloc())
val paint = NanoVG.nvgImagePattern(vg, x + imageOffset.x, y + imageOffset.y, image.size.width, image.size.height, 0f, image.id!!, 1.0F, NVGPaint.calloc())
NanoVG.nvgBeginPath(vg)
NanoVG.nvgRect(vg, x, y, width, height)
NanoVG.nvgFillPaint(vg, paint)
Expand All @@ -250,11 +250,11 @@ class TestRenderer : Renderer {
paint.free()
}

override fun roundedImage(x: Float, y: Float, width: Float, height: Float, radius: CornerRadius, image: ImageBuffer) {
override fun roundedImage(x: Float, y: Float, width: Float, height: Float, radius: CornerRadius, image: ImageBuffer, imageOffset: Offset) {
if (image.id == null)
throw IllegalStateException("Image was not registered properly!")

val paint = NanoVG.nvgImagePattern(vg, x, y, width, height, 0f, image.id!!, 1.0F, NVGPaint.calloc())
val paint = NanoVG.nvgImagePattern(vg, x + imageOffset.x, y + imageOffset.y, image.size.width, image.size.height, 0f, image.id!!, 1.0F, NVGPaint.calloc())
NanoVG.nvgBeginPath(vg)
NanoVG.nvgRoundedRectVarying(vg, x, y, width, height, radius.topLeft, radius.topRight, radius.bottomRight, radius.bottomLeft)
NanoVG.nvgFillPaint(vg, paint)
Expand Down
Binary file added src/test/resources/images/hampter2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 8c24bc3

Please sign in to comment.