diff --git a/worldwind-tutorials/src/jsMain/kotlin/earth/worldwind/tutorials/Main.kt b/worldwind-tutorials/src/jsMain/kotlin/earth/worldwind/tutorials/Main.kt index ccc4b1ca9..f7052c9e9 100644 --- a/worldwind-tutorials/src/jsMain/kotlin/earth/worldwind/tutorials/Main.kt +++ b/worldwind-tutorials/src/jsMain/kotlin/earth/worldwind/tutorials/Main.kt @@ -31,8 +31,7 @@ fun main() { "Paths" to PathsTutorial(wwd.engine), "Polygons" to PolygonsTutorial(wwd.engine), "Ellipses" to EllipsesTutorial(wwd.engine), - // TODO Uncomment when ImageSource.fromLineStipple will be implemented - //"Dash and fill" to ShapeDashAndFillTutorial(wwd.engine), + "Dash and fill" to ShapesDashAndFillTutorial(wwd.engine), "Labels" to LabelsTutorial(wwd.engine), "Sight line" to SightlineTutorial(wwd.engine), "Surface image" to SurfaceImageTutorial(wwd.engine), diff --git a/worldwind/src/androidMain/kotlin/earth/worldwind/render/image/ImageSource.kt b/worldwind/src/androidMain/kotlin/earth/worldwind/render/image/ImageSource.kt index 4acfd4ece..166c7fdde 100644 --- a/worldwind/src/androidMain/kotlin/earth/worldwind/render/image/ImageSource.kt +++ b/worldwind/src/androidMain/kotlin/earth/worldwind/render/image/ImageSource.kt @@ -1,7 +1,7 @@ package earth.worldwind.render.image import android.graphics.Bitmap -import android.graphics.Color.argb +import android.graphics.Color import androidx.annotation.DrawableRes import dev.icerock.moko.resources.ImageResource import earth.worldwind.util.AbstractSource @@ -265,27 +265,20 @@ actual open class ImageSource protected constructor(source: Any): AbstractSource protected open class LineStippleBitmapFactory(protected val factor: Int, protected val pattern: Short): BitmapFactory { override suspend fun createBitmap(): Bitmap { - val transparent = argb(0, 0, 0, 0) - val white = argb(255, 255, 255, 255) - return if (factor <= 0) { - val width = 16 - val pixels = IntArray(width) - pixels.fill(white) - val bitmap = Bitmap.createBitmap(width, 1 /*height*/, Bitmap.Config.ARGB_8888) - bitmap.setPixels(pixels, 0 /*offset*/, width /*stride*/, 0 /*x*/, 0 /*y*/, width, 1 /*height*/) - bitmap + val pixels = if (factor <= 0) { + IntArray(16) { Color.WHITE } } else { - val width = factor * 16 - val pixels = IntArray(width) - var pixel = 0 - for (bi in 0..15) { - val bit = pattern.toInt() and (1 shl bi) - val color = if (bit == 0) transparent else white - for (fi in 0 until factor) pixels[pixel++] = color + IntArray(factor * 16).also { pixels -> + var pixel = 0 + for (bi in 0..15) { + val bit = pattern.toInt() and (1 shl bi) + val color = if (bit == 0) Color.TRANSPARENT else Color.WHITE + for (fi in 0 until factor) pixels[pixel++] = color + } } - val bitmap = Bitmap.createBitmap(width, 1 /*height*/, Bitmap.Config.ARGB_8888) - bitmap.setPixels(pixels, 0 /*offset*/, width /*stride*/, 0 /*x*/, 0 /*y*/, width, 1 /*height*/) - bitmap + } + return Bitmap.createBitmap(pixels.size, 1, Bitmap.Config.ARGB_8888).apply { + setPixels(pixels, 0, pixels.size, 0, 0, pixels.size, 1) } } diff --git a/worldwind/src/jsMain/kotlin/earth/worldwind/render/RenderResourceCache.kt b/worldwind/src/jsMain/kotlin/earth/worldwind/render/RenderResourceCache.kt index 9ea790d3c..690ad192e 100644 --- a/worldwind/src/jsMain/kotlin/earth/worldwind/render/RenderResourceCache.kt +++ b/worldwind/src/jsMain/kotlin/earth/worldwind/render/RenderResourceCache.kt @@ -15,6 +15,9 @@ import earth.worldwind.util.kgl.* import kotlinx.browser.window import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch +import org.khronos.webgl.TexImageSource +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLImageElement import org.w3c.dom.Image import org.w3c.dom.url.URL import kotlin.time.Duration.Companion.seconds @@ -59,14 +62,14 @@ actual open class RenderResourceCache( when { imageSource.isImage -> { // Following type of image sources is already in memory, so a texture may be created and put into the cache immediately. - return createTexture(options, imageSource.asImage()).also { put(imageSource, it, it.byteCount) } + return createTexture(options, imageSource.asImage())?.also { put(imageSource, it, it.byteCount) } } imageSource.isImageFactory -> { val factory = imageSource.asImageFactory() if (factory.isRunBlocking) { // Image factory makes easy operations, so a texture may be created and put into the cache immediately. - return factory.createImage()?.let { bitmap -> - createTexture(options, bitmap).also { put(imageSource, it, it.byteCount) } + return factory.createImage()?.let { image -> + createTexture(options, image)?.also { put(imageSource, it, it.byteCount) } } } } @@ -122,25 +125,31 @@ actual open class RenderResourceCache( image.src = src } - protected open fun createTexture(options: ImageOptions?, image: Image): Texture { + protected open fun createTexture(options: ImageOptions?, image: TexImageSource): Texture? { + var (width, height) = when (image) { + is HTMLImageElement -> image.width to image.height + is HTMLCanvasElement -> image.width to image.height + else -> return null + } + // Process initialWidth and initialHeight if specified - if (image.width == 0 || image.height == 0) { + if (width == 0 || height == 0) { // If source image has dimensions, then resize it proportionally to fit initial size restrictions - val ratioW = if (options != null && options.initialWidth > 0) image.width / options.initialWidth else 0 - val ratioH = if (options != null && options.initialHeight > 0) image.height / options.initialHeight else 0 + val ratioW = if (options != null && options.initialWidth > 0) width / options.initialWidth else 0 + val ratioH = if (options != null && options.initialHeight > 0) height / options.initialHeight else 0 val ratio = if (ratioH > ratioW) ratioH else ratioW if (ratio > 0) { - image.width = image.width / ratio - image.height = image.height / ratio + width /= ratio + height /= ratio } } else if (options != null && options.initialWidth > 0 && options.initialHeight > 0) { // If source image has no dimensions (e.g. SVG image), then set initial size of image - image.width = options.initialWidth - image.height = options.initialHeight + width = options.initialWidth + height = options.initialHeight } // Create image texture and apply texture parameters - val texture = ImageTexture(image) + val texture = ImageTexture(image, width, height) if (options?.resamplingMode == ResamplingMode.NEAREST_NEIGHBOR) { texture.setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST) texture.setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST) @@ -152,10 +161,9 @@ actual open class RenderResourceCache( return texture } - protected open fun retrievalSucceeded(source: ImageSource, options: ImageOptions?, image: Image) { + protected open fun retrievalSucceeded(source: ImageSource, options: ImageOptions?, image: TexImageSource) { // Create texture and put it into cache. - val texture = createTexture(options, image) - put(source, texture, texture.byteCount) + createTexture(options, image)?.let { put(source, it, it.byteCount) } currentRetrievals -= source absentResourceList.unmarkResourceAbsent(source.hashCode()) WorldWind.requestRedraw() diff --git a/worldwind/src/jsMain/kotlin/earth/worldwind/render/TextRenderer.kt b/worldwind/src/jsMain/kotlin/earth/worldwind/render/TextRenderer.kt index 9fd680ca0..e8622c437 100644 --- a/worldwind/src/jsMain/kotlin/earth/worldwind/render/TextRenderer.kt +++ b/worldwind/src/jsMain/kotlin/earth/worldwind/render/TextRenderer.kt @@ -1,7 +1,7 @@ package earth.worldwind.render import earth.worldwind.geom.Vec2 -import earth.worldwind.render.image.CanvasTexture +import earth.worldwind.render.image.ImageTexture import earth.worldwind.shape.TextAttributes import kotlinx.browser.document import org.w3c.dom.* @@ -18,7 +18,7 @@ actual open class TextRenderer actual constructor(protected val rc: RenderContex * @returns A texture for the specified text string. */ actual fun renderText(text: String?, attributes: TextAttributes): Texture? = - if (text?.isNotEmpty() == true) CanvasTexture(drawText(text, attributes)) else null + if (text?.isNotEmpty() == true) ImageTexture(drawText(text, attributes)) else null /** * Creates a 2D Canvas for a specified text string while considering current TextRenderer state regarding outline diff --git a/worldwind/src/jsMain/kotlin/earth/worldwind/render/image/CanvasTexture.kt b/worldwind/src/jsMain/kotlin/earth/worldwind/render/image/CanvasTexture.kt deleted file mode 100644 index a0dd7bdee..000000000 --- a/worldwind/src/jsMain/kotlin/earth/worldwind/render/image/CanvasTexture.kt +++ /dev/null @@ -1,48 +0,0 @@ -package earth.worldwind.render.image - -import earth.worldwind.draw.DrawContext -import earth.worldwind.render.Texture -import earth.worldwind.util.Logger.ERROR -import earth.worldwind.util.Logger.logMessage -import earth.worldwind.util.kgl.GL_RGBA -import earth.worldwind.util.kgl.GL_TEXTURE_2D -import earth.worldwind.util.kgl.GL_UNSIGNED_BYTE -import earth.worldwind.util.kgl.WebKgl -import earth.worldwind.util.math.isPowerOfTwo -import org.khronos.webgl.WebGLRenderingContext.Companion.UNPACK_PREMULTIPLY_ALPHA_WEBGL -import org.w3c.dom.HTMLCanvasElement - -open class CanvasTexture(image: HTMLCanvasElement) : Texture(image.width, image.height, GL_RGBA, GL_UNSIGNED_BYTE) { - protected var image: HTMLCanvasElement? = image - override val hasMipMap = isPowerOfTwo(image.width) && isPowerOfTwo(image.height) - - init { - coordTransform.setToVerticalFlip() - } - - override fun release(dc: DrawContext) { - super.release(dc) - image = null // Image can be non-null if the texture has never been used - } - - override fun allocTexImage(dc: DrawContext) { - try { - // Specify the OpenGL texture 2D object's base image data (level 0). - dc.gl.pixelStorei(UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1) - (dc.gl as WebKgl).gl.texImage2D(GL_TEXTURE_2D, 0, format, format, type, image) - dc.gl.pixelStorei(UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0) - - // If the bitmap has power-of-two dimensions, generate the texture object's image data for image levels 1 - // through level N, and configure the texture object's filtering modes to use those image levels. - if (hasMipMap) dc.gl.generateMipmap(GL_TEXTURE_2D) - } catch (e: Exception) { - // The Android utility was unable to load the texture image data. - logMessage( - ERROR, "Texture", "loadTexImage", - "Exception attempting to load texture image '$image'", e - ) - } finally { - image = null - } - } -} diff --git a/worldwind/src/jsMain/kotlin/earth/worldwind/render/image/ImageSource.kt b/worldwind/src/jsMain/kotlin/earth/worldwind/render/image/ImageSource.kt index fec97d97b..ff7115c19 100644 --- a/worldwind/src/jsMain/kotlin/earth/worldwind/render/image/ImageSource.kt +++ b/worldwind/src/jsMain/kotlin/earth/worldwind/render/image/ImageSource.kt @@ -5,8 +5,10 @@ import earth.worldwind.util.AbstractSource import earth.worldwind.util.Logger.ERROR import earth.worldwind.util.Logger.logMessage import earth.worldwind.util.ResourcePostprocessor -import org.w3c.dom.Image +import org.w3c.dom.* import org.w3c.dom.url.URL +import kotlinx.browser.document +import org.khronos.webgl.TexImageSource /** * Provides a mechanism for specifying images from a variety of sources. ImageSource retains the image source on behalf @@ -23,6 +25,7 @@ import org.w3c.dom.url.URL */ actual open class ImageSource protected constructor(source: Any): AbstractSource(source) { actual companion object { + protected val lineStippleFactories = mutableMapOf() /** * Constructs an image source with a multi-platform resource identifier. * @@ -91,7 +94,13 @@ actual open class ImageSource protected constructor(source: Any): AbstractSource * @return the new image source */ actual fun fromLineStipple(factor: Int, pattern: Short): ImageSource { - TODO("Not yet implemented") + val lFactor = factor.toLong() and 0xFFFFFFFFL + val lPattern = pattern.toLong() and 0xFFFFL + val key = lFactor shl 32 or lPattern + val factory = lineStippleFactories[key] ?: LineStippleImageFactory(factor, pattern).also { + lineStippleFactories[key] = it + } + return ImageSource(factory) } /** @@ -182,6 +191,36 @@ actual open class ImageSource protected constructor(source: Any): AbstractSource * * @return the image associated with this factory */ - fun createImage(): Image? + fun createImage(): TexImageSource? } + + protected open class LineStippleImageFactory(protected val factor: Int, protected val pattern: Short): ImageFactory { + override fun createImage(): TexImageSource { + val canvas = document.createElement("canvas") as HTMLCanvasElement + canvas.width = if (factor <= 0) 16 else factor * 16 + canvas.height = 1 + val ctx2D = canvas.getContext("2d") as CanvasRenderingContext2D + ctx2D.strokeStyle = "white" + ctx2D.beginPath() + if (factor <= 0) { + ctx2D.moveTo(0.0, 0.5) + ctx2D.lineTo(canvas.width.toDouble(), 0.5) + } else { + var offset = 0 + for (bi in 0..15) { + val bit = pattern.toInt() and (1 shl bi) + if (bit != 0) { + ctx2D.moveTo(offset.toDouble(), 0.5) + ctx2D.lineTo((offset + factor).toDouble(), 0.5) + } + offset += factor + } + } + ctx2D.stroke() + return canvas + } + + override fun toString() = "LineStippleCanvasFactory factor=$factor, pattern=" + (pattern.toInt() and 0xFFFF).toString(16).uppercase() + } + } \ No newline at end of file diff --git a/worldwind/src/jsMain/kotlin/earth/worldwind/render/image/ImageTexture.kt b/worldwind/src/jsMain/kotlin/earth/worldwind/render/image/ImageTexture.kt index 1fa8802e1..4a7d39056 100644 --- a/worldwind/src/jsMain/kotlin/earth/worldwind/render/image/ImageTexture.kt +++ b/worldwind/src/jsMain/kotlin/earth/worldwind/render/image/ImageTexture.kt @@ -9,12 +9,18 @@ import earth.worldwind.util.kgl.GL_TEXTURE_2D import earth.worldwind.util.kgl.GL_UNSIGNED_BYTE import earth.worldwind.util.kgl.WebKgl import earth.worldwind.util.math.isPowerOfTwo +import org.khronos.webgl.TexImageSource import org.khronos.webgl.WebGLRenderingContext.Companion.UNPACK_PREMULTIPLY_ALPHA_WEBGL -import org.w3c.dom.Image +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLImageElement -open class ImageTexture(image: Image) : Texture(image.width, image.height, GL_RGBA, GL_UNSIGNED_BYTE) { - protected var image: Image? = image - override val hasMipMap = isPowerOfTwo(image.width) && isPowerOfTwo(image.height) +open class ImageTexture(image: TexImageSource, width: Int, height: Int) : Texture(width, height, GL_RGBA, GL_UNSIGNED_BYTE) { + protected var image: TexImageSource? = image + override val hasMipMap = isPowerOfTwo(width) && isPowerOfTwo(height) + + constructor(image: HTMLImageElement) : this(image, image.width, image.height) + + constructor(image: HTMLCanvasElement) : this(image, image.width, image.height) init { coordTransform.setToVerticalFlip()