Skip to content

Commit

Permalink
add documentation on multi dpi assets
Browse files Browse the repository at this point in the history
  • Loading branch information
regb committed Aug 11, 2023
1 parent 55ea924 commit 9f23f5f
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 9 deletions.
3 changes: 0 additions & 3 deletions android/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
// This dependency fixes a Class Not Found exception when using Java 11+ for SBT 0.13
//libraryDependencies += "javax.xml.bind" % "jaxb-api" % "2.3.1"

addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.10")
8 changes: 8 additions & 0 deletions core/src/main/scala/sgl/GraphicsHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,23 @@ private[sgl] trait GraphicsHelpersComponent {
}
}

/** Draw the region at the point (x,y) on the canvas.
*
* This will use the size of the bitmap region to draw as is (no scaling) in the canvas.
*/
def drawBitmap(region: BitmapRegion, x: Float, y: Float): Unit = {
this.drawBitmap(region.bitmap, x, y, region.x, region.y, region.width, region.height)
}

/** Draw the region scaled by a factor s at the point (x,y) on the canvas. */
def drawBitmap(region: BitmapRegion, x: Float, y: Float, s: Float): Unit = {
this.drawBitmap(region.bitmap, x, y, region.x, region.y, region.width, region.height, s, 1f)
}

def drawBitmap(region: BitmapRegion, x: Float, y: Float, s: Float, alpha: Float): Unit = {
this.drawBitmap(region.bitmap, x, y, region.x, region.y, region.width, region.height, s, alpha)
}

/** Draw the region into the rectangle (x,y,w,h) on the canvas.
*
* This will scale the bitmap region to fit the rectangle.
Expand Down
38 changes: 37 additions & 1 deletion core/src/main/scala/sgl/GraphicsProvider.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,48 @@ trait GraphicsProvider extends GraphicsHelpersComponent {

trait Graphics extends GraphicsExtension {

/** Load a bitmap identified by a resource path.
*
* Note that this automatically adapts the bitmap to the screen density.
* You can provide several version of the same resources, for targeting
* multiple screen densities (mdpi, hdpi, xhdpi, etc, following Android conventions).
* This function will automatically choose the most appropriate resource and load it.
*
* In addition, it will then scale the bitmap to be the expected size given the provided
* density and the actual density. So, for example, if you provide the asset only
* in `drawable-mdpi`, and the platform where you are running has a `xhdpi` density,
* then the bitmap returned will be loaded from the `mdpi` resource and then scaled 2x
* (mdpi is 160ppi, xhdpi is 320ppi, so 320/160 = 2). The returned Bitmap will be
* bigger than the asset provided on disk.
*/
def loadImage(path: ResourcePath): Loader[Bitmap]

abstract class AbstractBitmap {
def height: Int

/** The width in pixel of the bitmap, in memory.
*
* Note that this is not necessary the width of the
* resource on file, it is scaled automatically by SGL
* depending on the screen density (see Window.LogicalPpi).
*
* Typically you will provide several resolutions for the same
* game assets, one per screen density (in the `drawable-?dpi` folders),
* and the system will load the most appropriate one. In addition the
* system could scale up or down whatever it has loaded, to adapt it
* to the actual screen dpi (mdpi -> 160, hdpi -> 240, xhdpi -> 320, etc).
*
* In general, if you want consistent look across platforms, you probably
* don't want to rely on the width of the bitmap but instead use a world
* coordinates system and draw the bitmap into that system.
*/
def width: Int

/** The height in pixel of the bitmap, in memory.
*
* Same notes as for width.
*/
def height: Int

/** Release the bitmap resources.
*
* Once you no longer need the bitmap, you should explicitly
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/scala/sgl/scene/SceneGraph.scala
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,10 @@ trait SceneGraphComponent {
* Update does nothing (it's a static bitmap) and render renders it.
*/
class BitmapNode(bitmap: Graphics.BitmapRegion, _x: Float, _y: Float) extends SceneNode(_x, _y, bitmap.width, bitmap.height) {
// TODO: this is probably not a good design to have such a node, instead we should migrate towards game object + attached
// components, and to render simple bitmaps we should just attach a sprite renderer.
// One problem with this is that it doesn't adapt well to the automated scaling of bitmaps depending on density.

// A bitmap is just static, so nothing to do on update.
override def update(dt: Long): Unit = {}
override def render(canvas: Graphics.Canvas): Unit = canvas.drawBitmap(bitmap, this.x, this.y)
Expand Down
17 changes: 13 additions & 4 deletions desktop-awt/src/main/scala/sgl/awt/AWTGraphicsProvider.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package awt
import sgl.util._
import awt.util._

import java.awt.{FontMetrics, Image, Graphics, Graphics2D, Color, AlphaComposite, GraphicsEnvironment, GraphicsConfiguration, Transparency}
import java.awt.{RenderingHints, FontMetrics, Image, Graphics, Graphics2D, Color, AlphaComposite, GraphicsEnvironment, GraphicsConfiguration, Transparency}
import java.awt.image.BufferedImage
import java.awt.geom.{Rectangle2D, Ellipse2D, Line2D, AffineTransform}
import javax.imageio.ImageIO
Expand All @@ -20,23 +20,32 @@ trait AWTGraphicsProvider extends GraphicsProvider {
override def loadImage(path: ResourcePath): Loader[Bitmap] = {
FutureLoader {
// TODO: support multi dpi in desktop version.
// For now we just always use mdpi assets and scale them to map to the ppi.
val mdpiPath = PartsResourcePath(Vector("drawable-mdpi") ++ path.parts)
val localAsset = if(DynamicResourcesEnabled) findDynamicResource(mdpiPath) else None
val url = localAsset.map(_.toURI.toURL).getOrElse(getClass.getClassLoader.getResource(mdpiPath.path))
if(url == null) {
throw new ResourceNotFoundException(mdpiPath)
}

// TODO: we need to scale the image to the actual DPI of the screen.
// MDPI is meant for 160dpi, so we check Window.logicalPpi to determine how to scale the asset.
// This is similar to how Android loads drawables and then scale them to fit the actual dpi of the screen.
// However Android also selects the graphics from the closest density provided (mdpi/hdpi/etc) and only then
// scale it, but that's something we will implement eventually.
// TODO: We want to select the asset from the most appropriate drawable-?dpi folder.
val scalingFactor = Window.logicalPpi / 160f

val bufferedImage = {
val sb = ImageIO.read(url)
val scaledWidth = (scalingFactor*sb.getWidth).toInt
val scaledHeight = (scalingFactor*sb.getHeight).toInt
// Now we copy the read buffered image into a new buffered image which
// has a compatibly TYPE_INT_ARGB with the target buffer in which we will
// render the image.
val b = AWTGraphicsConfig.createCompatibleImage(sb.getWidth, sb.getHeight, Transparency.TRANSLUCENT)
val b = AWTGraphicsConfig.createCompatibleImage(scaledWidth, scaledHeight, Transparency.TRANSLUCENT)
val g = b.createGraphics
g.drawImage(sb, 0, 0, null)
g.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
g.drawImage(sb, 0, 0, scaledWidth, scaledHeight, null)
g.dispose()
sb.flush()
b
Expand Down
3 changes: 2 additions & 1 deletion desktop-awt/src/main/scala/sgl/awt/AWTWindowProvider.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ trait AWTWindowProvider extends WindowProvider {
type Window = AWTWindow
override val Window = new AWTWindow

/** Override this if you want to force an arbitrary PPI. */
/** Override this if you want to force an arbitrary PPI. Typically it's useful for testing how your game will adapt
* to multiple screen densities, instead of testing on multiple platforms. */
val ScreenForcePPI: Option[Float] = None
}
2 changes: 2 additions & 0 deletions examples/hello/desktop-awt/src/main/scala/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ object Main extends AbstractApp with AWTApp

override val frameDimension = (800, 800)

override val ScreenForcePPI = Some(160)

}

0 comments on commit 9f23f5f

Please sign in to comment.