Skip to content

Commit

Permalink
Refactor transparency log
Browse files Browse the repository at this point in the history
  • Loading branch information
gino-m committed Sep 10, 2023
1 parent 21fa63b commit 2b68694
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,14 @@

package com.google.android.ground.ui.map.gms

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import com.google.android.gms.maps.model.LatLngBounds
import com.google.android.gms.maps.model.Tile
import com.google.android.gms.maps.model.TileProvider
import com.google.android.gms.maps.model.TileProvider.NO_TILE
import com.google.android.ground.ui.map.gms.mog.ImageEditor
import com.google.android.ground.ui.map.gms.mog.TileCoordinates
import com.google.android.ground.ui.map.gms.mog.toPixelBounds
import com.google.android.ground.ui.map.gms.mog.toPixelCoordinate
import java.io.ByteArrayOutputStream

private const val MAX_ZOOM = 19

Expand All @@ -47,24 +44,11 @@ class ClippingTileProvider(

private fun clipToBounds(tileCoords: TileCoordinates, tile: Tile): Tile {
if (tile.data == null) return NO_TILE
val opts = BitmapFactory.Options()
opts.inMutable = true
val bitmap = BitmapFactory.decodeByteArray(tile.data, 0, tile.data!!.size, opts)
bitmap.setHasAlpha(true)
for (y in 0 until bitmap.height) {
for (x in 0 until bitmap.width) {
val output =
ImageEditor.setTransparentIf(tile.data!!) { _, x, y ->
val pixelCoords = tileCoords.toPixelCoordinate(x, y)
if (pixelBounds.none { it.contains(pixelCoords) }) {
bitmap.setPixel(x, y, Color.TRANSPARENT)
}
pixelBounds.none { it.contains(pixelCoords) }
}
}
// Android doesn't implement encoders for uncompressed format, so we must compress the returned
// tile so that it can be later encoded by Maps SDK. Experimentally, decompressing JPG and
// compressing WEBP each tile adds on the order of 1ms to each tile which we can consider
// negligible.
val stream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.WEBP, 100, stream)
return Tile(tile.width, tile.height, stream.toByteArray())
return Tile(tile.width, tile.height, output)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2023 Google LLC
*
* 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
*
* https://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 com.google.android.ground.ui.map.gms.mog

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import java.io.ByteArrayOutputStream

/** Utility for modifying images. */
object ImageEditor {
/**
* Decodes the provided source image, making all pixels for which the provided lambda returns
* `true` fully transparent (alpha = 0). Returns the modified images as a byte array.
*/
fun setTransparentIf(
sourceImage: ByteArray,
isTransparent: (bitmap: Bitmap, x: Int, y: Int) -> Boolean
): ByteArray {
val opts = BitmapFactory.Options()
opts.inMutable = true
val bitmap = BitmapFactory.decodeByteArray(sourceImage, 0, sourceImage.size, opts)
bitmap.setHasAlpha(true)
for (y in 0 until bitmap.height) {
for (x in 0 until bitmap.width) {
if (isTransparent(bitmap, x, y)) {
bitmap.setPixel(x, y, Color.TRANSPARENT)
}
}
}
// Android doesn't implement encoders for uncompressed format, so we must compress the returned
// tile so that it can be later encoded by Maps SDK. Experimentally, decompressing JPG and
// compressing WEBP each tile adds on the order of 1ms to each tile which we can consider
// negligible.
val stream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.WEBP, 100, stream)
return stream.toByteArray()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@

package com.google.android.ground.ui.map.gms.mog

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import com.google.android.gms.maps.model.Tile
import java.io.ByteArrayOutputStream

/** Metadata and image data for a single tile. */
class MogTile(val metadata: MogTileMetadata, val data: ByteArray) {
Expand All @@ -31,32 +28,15 @@ class MogTile(val metadata: MogTileMetadata, val data: ByteArray) {
.toByteArray()

fun toGmsTile(): Tile {
val noData = metadata.noDataValue
val imageData =
if (metadata.noDataValue == null) buildJfifFile()
else maskNoDataValues(buildJfifFile(), metadata.noDataValue)
if (noData == null) buildJfifFile()
else maskNoDataValues(buildJfifFile(), Color.rgb(noData, noData, noData))
return Tile(metadata.width, metadata.height, imageData)
}

private fun maskNoDataValues(jfifData: ByteArray, noDataValue: Int): ByteArray {
val opts = BitmapFactory.Options()
opts.inMutable = true
val bitmap = BitmapFactory.decodeByteArray(jfifData, 0, jfifData.size, opts)
bitmap.setHasAlpha(true)
for (y in 0 until bitmap.height) {
for (x in 0 until bitmap.width) {
if (bitmap.getPixel(x, y) == Color.rgb(noDataValue, noDataValue, noDataValue)) {
bitmap.setPixel(x, y, Color.TRANSPARENT)
}
}
}
// Android doesn't implement encoders for uncompressed format, so we must compress the returned
// tile so that it can be later encoded by Maps SDK. Experimentally, decompressing JPG and
// compressing WEBP each tile adds on the order of 1ms to each tile which we can consider
// negligible.
val stream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.WEBP, 100, stream)
return stream.toByteArray()
}
private fun maskNoDataValues(jfifData: ByteArray, noDataColor: Int): ByteArray =
ImageEditor.setTransparentIf(jfifData) { bitmap, x, y -> bitmap.getPixel(x, y) == noDataColor }

private fun buildJfifFile(): ByteArray =
START_OF_IMAGE +
Expand Down

0 comments on commit 2b68694

Please sign in to comment.