diff --git a/README.md b/README.md index fa11f604..36912c96 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Kim - Kotlin Image Metadata -[![Kotlin](https://img.shields.io/badge/kotlin-2.1.0-blue.svg?logo=kotlin)](httpw://kotlinlang.org) +[![Kotlin](https://img.shields.io/badge/kotlin-2.1.10-blue.svg?logo=kotlin)](httpw://kotlinlang.org) ![JVM](https://img.shields.io/badge/-JVM-gray.svg?style=flat) ![Android](https://img.shields.io/badge/-Android-gray.svg?style=flat) ![iOS](https://img.shields.io/badge/-iOS-gray.svg?style=flat) @@ -39,7 +39,7 @@ of Ashampoo Photo Organizer, which, in turn, is driven by user community feedbac ## Installation ``` -implementation("com.ashampoo:kim:0.21") +implementation("com.ashampoo:kim:0.22") ``` For the targets `wasmJs` & `js` you also need to specify this: diff --git a/build.gradle.kts b/build.gradle.kts index c8538206..a084904e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework plugins { - kotlin("multiplatform") version "2.1.0" + kotlin("multiplatform") version "2.1.10" id("com.android.library") version "8.5.0" id("maven-publish") id("signing") diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 18362b78..9bf7bd33 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/JpegUpdater.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/JpegUpdater.kt index a8c30349..8d44355e 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/JpegUpdater.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/JpegUpdater.kt @@ -127,7 +127,8 @@ internal object JpegUpdater : MetadataUpdater { update !is MetadataUpdate.Orientation && update !is MetadataUpdate.TakenDate && update !is MetadataUpdate.Description && - update !is MetadataUpdate.GpsCoordinates + update !is MetadataUpdate.GpsCoordinates && + update !is MetadataUpdate.GpsCoordinatesAndLocationShown ) return inputBytes @@ -191,6 +192,7 @@ internal object JpegUpdater : MetadataUpdater { update !is MetadataUpdate.Title && update !is MetadataUpdate.Description && update !is MetadataUpdate.LocationShown && + update !is MetadataUpdate.GpsCoordinatesAndLocationShown && update !is MetadataUpdate.Keywords ) return inputBytes @@ -246,6 +248,32 @@ internal object JpegUpdater : MetadataUpdater { } } + if (update is MetadataUpdate.GpsCoordinatesAndLocationShown) { + + newRecords.addAll( + oldRecords.filter { + it.iptcType != IptcTypes.CITY && + it.iptcType != IptcTypes.PROVINCE_STATE && + it.iptcType != IptcTypes.COUNTRY_PRIMARY_LOCATION_NAME + } + ) + + if (update.locationShown != null) { + + update.locationShown.city?.let { city -> + newRecords.add(IptcRecord(IptcTypes.CITY, city)) + } + + update.locationShown.state?.let { state -> + newRecords.add(IptcRecord(IptcTypes.PROVINCE_STATE, state)) + } + + update.locationShown.country?.let { country -> + newRecords.add(IptcRecord(IptcTypes.COUNTRY_PRIMARY_LOCATION_NAME, country)) + } + } + } + if (update is MetadataUpdate.Keywords) { newRecords.addAll(oldRecords.filter { it.iptcType != IptcTypes.KEYWORDS }) diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/JxlUpdater.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/JxlUpdater.kt index ef7d8def..c663685a 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/JxlUpdater.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/JxlUpdater.kt @@ -55,10 +55,12 @@ internal object JxlUpdater : MetadataUpdater { val updatedXmp = XmpWriter.updateXmp(xmpMeta, update, true) - val isExifUpdate = update is MetadataUpdate.Orientation || - update is MetadataUpdate.TakenDate || - update is MetadataUpdate.Description || - update is MetadataUpdate.GpsCoordinates + val isExifUpdate = + update is MetadataUpdate.Orientation || + update is MetadataUpdate.TakenDate || + update is MetadataUpdate.Description || + update is MetadataUpdate.GpsCoordinates || + update is MetadataUpdate.GpsCoordinatesAndLocationShown val exifBytes: ByteArray? = if (isExifUpdate) { @@ -131,6 +133,6 @@ internal object JxlUpdater : MetadataUpdater { xmp = null // No change to XMP ) - return byteWriter.toByteArray() + return@tryWithImageWriteException byteWriter.toByteArray() } } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/png/PngUpdater.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/png/PngUpdater.kt index 05d89109..66e972fc 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/png/PngUpdater.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/png/PngUpdater.kt @@ -51,10 +51,12 @@ internal object PngUpdater : MetadataUpdater { val updatedXmp = XmpWriter.updateXmp(xmpMeta, update, true) - val isExifUpdate = update is MetadataUpdate.Orientation || - update is MetadataUpdate.TakenDate || - update is MetadataUpdate.Description || - update is MetadataUpdate.GpsCoordinates + val isExifUpdate = + update is MetadataUpdate.Orientation || + update is MetadataUpdate.TakenDate || + update is MetadataUpdate.Description || + update is MetadataUpdate.GpsCoordinates || + update is MetadataUpdate.GpsCoordinatesAndLocationShown val exifBytes: ByteArray? = if (isExifUpdate) { @@ -131,6 +133,6 @@ internal object PngUpdater : MetadataUpdater { xmp = null // No change to XMP ) - return@updateThumbnail byteWriter.toByteArray() + return@tryWithImageWriteException byteWriter.toByteArray() } } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/write/TiffOutputSet.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/write/TiffOutputSet.kt index 73616c15..b50ecdea 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/write/TiffOutputSet.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/write/TiffOutputSet.kt @@ -142,6 +142,11 @@ public class TiffOutputSet( setGpsCoordinates(update.gpsCoordinates) } + is MetadataUpdate.GpsCoordinatesAndLocationShown -> { + + setGpsCoordinates(update.gpsCoordinates) + } + else -> throw ImageWriteException("Can't perform update $update.") } } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/webp/WebPUpdater.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/webp/WebPUpdater.kt index 2a826217..0a2a8bf5 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/webp/WebPUpdater.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/webp/WebPUpdater.kt @@ -51,10 +51,12 @@ internal object WebPUpdater : MetadataUpdater { val updatedXmp = XmpWriter.updateXmp(xmpMeta, update, true) - val isExifUpdate = update is MetadataUpdate.Orientation || - update is MetadataUpdate.TakenDate || - update is MetadataUpdate.Description || - update is MetadataUpdate.GpsCoordinates + val isExifUpdate = + update is MetadataUpdate.Orientation || + update is MetadataUpdate.TakenDate || + update is MetadataUpdate.Description || + update is MetadataUpdate.GpsCoordinates || + update is MetadataUpdate.GpsCoordinatesAndLocationShown val exifBytes: ByteArray? = if (isExifUpdate) { @@ -124,6 +126,6 @@ internal object WebPUpdater : MetadataUpdater { xmp = null // No change to XMP ) - return byteWriter.toByteArray() + return@tryWithImageWriteException byteWriter.toByteArray() } } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/xmp/XmpWriter.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/xmp/XmpWriter.kt index 6ce818c2..e69f5574 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/xmp/XmpWriter.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/xmp/XmpWriter.kt @@ -99,6 +99,38 @@ public object XmpWriter { ) } + is MetadataUpdate.GpsCoordinatesAndLocationShown -> { + + /* GPS */ + + if (update.gpsCoordinates != null) + setGpsCoordinates( + GpsUtil.decimalLatitudeToDDM(update.gpsCoordinates.latitude), + GpsUtil.decimalLongitudeToDDM(update.gpsCoordinates.longitude) + ) + else + deleteGpsCoordinates() + + /* Location */ + + val locationShown = update.locationShown + + if (locationShown == null) { + setLocation(null) + return + } + + setLocation( + XMPLocation( + name = locationShown.name, + location = locationShown.location, + city = locationShown.city, + state = locationShown.state, + country = locationShown.country + ) + ) + } + is MetadataUpdate.Title -> setTitle(update.title) diff --git a/src/commonMain/kotlin/com/ashampoo/kim/model/GpsCoordinates.kt b/src/commonMain/kotlin/com/ashampoo/kim/model/GpsCoordinates.kt index 7e18e548..d6f6e92b 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/model/GpsCoordinates.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/model/GpsCoordinates.kt @@ -23,29 +23,72 @@ private const val MIN_LATITUDE: Double = -MAX_LATITUDE private const val MAX_LONGITUDE = 180.0 private const val MIN_LONGITUDE = -MAX_LONGITUDE +/** Around ~100 m accuracy */ private const val THREE_DIGIT_PRECISE: Double = 1_000.0 + +/** Around ~1 m accuracy */ private const val FIVE_DIGIT_PRECISE: Double = 100_000.0 +private const val LAT_LONG_STRING_REGEX_PATTERN = + """^\s*-?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*-?(180(\.0+)?|((1[0-7]\d)|(\d{1,2}))(\.\d+)?)\s*$""" + +private val latLongStringRegex: Regex = LAT_LONG_STRING_REGEX_PATTERN.toRegex() + public data class GpsCoordinates( val latitude: Double, val longitude: Double ) { - val displayString: String = "GPS: ${roundForDisplay(latitude)}, ${roundForDisplay(longitude)}" + val latLongString: String = "${roundPrecise(latitude)}, ${roundPrecise(longitude)}" - public fun toRoundedForCaching(): GpsCoordinates = GpsCoordinates( - latitude = roundForCaching(latitude), - longitude = roundForCaching(longitude) + public fun toPreciseCoordinates(): GpsCoordinates = GpsCoordinates( + latitude = roundPrecise(latitude), + longitude = roundPrecise(longitude) ) - public fun isNullIsland(): Boolean = latitude == 0.0 && longitude == 0.0 + public fun toCoarseCoordinates(): GpsCoordinates = GpsCoordinates( + latitude = roundCoarse(latitude), + longitude = roundCoarse(longitude) + ) + + public fun isNullIsland(): Boolean = + latitude == 0.0 && longitude == 0.0 public fun isValid(): Boolean = - latitude in MIN_LATITUDE..MAX_LATITUDE && longitude in MIN_LONGITUDE..MAX_LONGITUDE + latitude in MIN_LATITUDE..MAX_LATITUDE && + longitude in MIN_LONGITUDE..MAX_LONGITUDE + + public companion object { + + public fun parse(latLongString: String?): GpsCoordinates? { + + if (latLongString.isNullOrBlank()) + return null + + if (!latLongStringRegex.matches(latLongString)) + return null + + val parts = latLongString.split(",") + + return GpsCoordinates( + latitude = parts[0].toDouble(), + longitude = parts[1].toDouble() + ) + } + } } -private fun roundForCaching(value: Double): Double = +/** + * Rounds the coordinates to three decimal places, + * providing approximately 100 meters accuracy. + */ +private fun roundCoarse(value: Double): Double = round(value * THREE_DIGIT_PRECISE) / THREE_DIGIT_PRECISE -private fun roundForDisplay(value: Double): Double = +/** + * Rounds the coordinates to five decimal places, + * providing approximately 1 meter accuracy. + * Suitable for display and precise localization. + */ +private fun roundPrecise(value: Double): Double = round(value * FIVE_DIGIT_PRECISE) / FIVE_DIGIT_PRECISE diff --git a/src/commonMain/kotlin/com/ashampoo/kim/model/MetadataUpdate.kt b/src/commonMain/kotlin/com/ashampoo/kim/model/MetadataUpdate.kt index 87f86be1..0de7effe 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/model/MetadataUpdate.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/model/MetadataUpdate.kt @@ -52,6 +52,14 @@ public sealed interface MetadataUpdate { val locationShown: com.ashampoo.kim.model.LocationShown? ) : MetadataUpdate + /* + * One-shot update GPS coordinates & location + */ + public data class GpsCoordinatesAndLocationShown( + val gpsCoordinates: com.ashampoo.kim.model.GpsCoordinates?, + val locationShown: com.ashampoo.kim.model.LocationShown? + ) : MetadataUpdate + /** * New title or NULL to remove it */ diff --git a/src/commonMain/kotlin/com/ashampoo/kim/model/PhotoMetadata.kt b/src/commonMain/kotlin/com/ashampoo/kim/model/PhotoMetadata.kt index aa796679..93baa13f 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/model/PhotoMetadata.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/model/PhotoMetadata.kt @@ -75,7 +75,8 @@ public data class PhotoMetadata( (widthPx * heightPx).div(PhotoValueFormatter.MEGA_PIXEL_COUNT) val locationDisplay: String? - get() = locationShown?.displayString ?: gpsCoordinates?.let { gpsCoordinates.displayString } + get() = locationShown?.displayString + ?: gpsCoordinates?.let { "GPS: " + gpsCoordinates.latLongString } val cameraName: String? get() = PhotoValueFormatter.createCameraOrLensName(cameraMake, cameraModel) diff --git a/src/commonTest/kotlin/com/ashampoo/kim/format/AbstractUpdaterTest.kt b/src/commonTest/kotlin/com/ashampoo/kim/format/AbstractUpdaterTest.kt index f754a047..8ba7b664 100644 --- a/src/commonTest/kotlin/com/ashampoo/kim/format/AbstractUpdaterTest.kt +++ b/src/commonTest/kotlin/com/ashampoo/kim/format/AbstractUpdaterTest.kt @@ -162,6 +162,34 @@ abstract class AbstractUpdaterTest( compare("new_location_shown.no_metadata.$format", newBytes) } + @Test + fun testUpdateGpsCoordinatesAndLocationShown() { + + val newBytes = Kim.update( + bytes = originalBytes, + update = MetadataUpdate.GpsCoordinatesAndLocationShown( + gpsCoordinates = crashBuildingGps, + locationShown = crashBuildingLocation + ) + ) + + compare("new_gps_coordinates_and_location_shown.$format", newBytes) + } + + @Test + fun testUpdateGpsCoordinatesAndLocationShownOnEmptyImage() { + + val newBytes = Kim.update( + bytes = noMetadataBytes, + update = MetadataUpdate.GpsCoordinatesAndLocationShown( + gpsCoordinates = crashBuildingGps, + locationShown = crashBuildingLocation + ) + ) + + compare("new_gps_coordinates_and_location_shown.no_metadata.$format", newBytes) + } + @Test fun testUpdateTitle() { diff --git a/src/commonTest/kotlin/com/ashampoo/kim/model/GpsCoordinatesTest.kt b/src/commonTest/kotlin/com/ashampoo/kim/model/GpsCoordinatesTest.kt new file mode 100644 index 00000000..97faee85 --- /dev/null +++ b/src/commonTest/kotlin/com/ashampoo/kim/model/GpsCoordinatesTest.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2025 Ashampoo GmbH & Co. KG + * + * 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 + * + * http://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.ashampoo.kim.model + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class GpsCoordinatesTest { + + @Test + fun testLatLongString() { + + assertEquals( + expected = "53.21939, 8.23966", + actual = GpsCoordinates( + latitude = 53.2193897123, + longitude = 8.2396611123 + ).latLongString + ) + } + + @Test + fun testPreciseCoordinates() { + + assertEquals( + expected = GpsCoordinates( + latitude = 53.21939, + longitude = 8.23966 + ), + actual = GpsCoordinates( + latitude = 53.2193897123, + longitude = 8.2396611123 + ).toPreciseCoordinates() + ) + } + + @Test + fun testCoarseCoordinates() { + + assertEquals( + expected = GpsCoordinates( + latitude = 53.219, + longitude = 8.24 + ), + actual = GpsCoordinates( + latitude = 53.2193897123, + longitude = 8.2396611123 + ).toCoarseCoordinates() + ) + } + + @Test + fun testIsNullIsland() { + + assertTrue( + GpsCoordinates( + latitude = 0.0, + longitude = 0.0 + ).isNullIsland() + ) + + assertFalse( + GpsCoordinates( + latitude = 53.2193897123, + longitude = 8.2396611123 + ).isNullIsland() + ) + } + + @Test + fun testIsValid() { + + /* Valid values */ + + assertTrue( + GpsCoordinates( + latitude = 53.2193897123, + longitude = 8.2396611123 + ).isValid() + ) + + /* Edge values */ + + assertTrue( + GpsCoordinates( + latitude = 90.0, + longitude = 180.0 + ).isValid() + ) + + assertTrue( + GpsCoordinates( + latitude = -90.0, + longitude = -180.0 + ).isValid() + ) + + /* Invalid values */ + + assertFalse( + GpsCoordinates( + latitude = 91.0, + longitude = 180.0 + ).isValid() + ) + + assertFalse( + GpsCoordinates( + latitude = -90.0, + longitude = -181.0 + ).isValid() + ) + + assertFalse( + GpsCoordinates( + latitude = 0.0, + longitude = 200.0 + ).isValid() + ) + + assertFalse( + GpsCoordinates( + latitude = 200.0, + longitude = 200.0 + ).isValid() + ) + } + + @Test + fun testParseEmptyValues() { + + assertNull(GpsCoordinates.parse(null)) + assertNull(GpsCoordinates.parse("")) + assertNull(GpsCoordinates.parse(" ")) + assertNull(GpsCoordinates.parse("hello")) + } + + @Test + fun testParseValidValues() { + + assertEquals( + expected = GpsCoordinates( + latitude = 53.219391, + longitude = 8.239661 + ), + actual = GpsCoordinates.parse("53.219391,8.239661") + ) + + assertEquals( + expected = GpsCoordinates( + latitude = 53.219391, + longitude = 8.239661 + ), + actual = GpsCoordinates.parse("53.219391, 8.239661") + ) + + assertEquals( + expected = GpsCoordinates( + latitude = 53.219391, + longitude = 8.239661 + ), + actual = GpsCoordinates.parse(" 53.219391, 8.239661 ") + ) + + /* Edge values */ + + assertEquals( + expected = GpsCoordinates( + latitude = -90.0, + longitude = -180.0 + ), + actual = GpsCoordinates.parse("-90.0, -180.0") + ) + + assertEquals( + expected = GpsCoordinates( + latitude = 90.0, + longitude = 180.0 + ), + actual = GpsCoordinates.parse("90.0, 180.0") + ) + } + + @Test + fun testParseInvalidValues() { + + assertNull(GpsCoordinates.parse("91.0, 45.0")) + assertNull(GpsCoordinates.parse("60.0, 190.0")) + } +} diff --git a/src/commonTest/resources/com/ashampoo/kim/updates_jpg/new_gps_coordinates_and_location_shown.jpg b/src/commonTest/resources/com/ashampoo/kim/updates_jpg/new_gps_coordinates_and_location_shown.jpg new file mode 100644 index 00000000..63df8f63 Binary files /dev/null and b/src/commonTest/resources/com/ashampoo/kim/updates_jpg/new_gps_coordinates_and_location_shown.jpg differ diff --git a/src/commonTest/resources/com/ashampoo/kim/updates_jpg/new_gps_coordinates_and_location_shown.no_metadata.jpg b/src/commonTest/resources/com/ashampoo/kim/updates_jpg/new_gps_coordinates_and_location_shown.no_metadata.jpg new file mode 100644 index 00000000..7bb38f6a Binary files /dev/null and b/src/commonTest/resources/com/ashampoo/kim/updates_jpg/new_gps_coordinates_and_location_shown.no_metadata.jpg differ diff --git a/src/commonTest/resources/com/ashampoo/kim/updates_jxl/new_gps_coordinates_and_location_shown.jxl b/src/commonTest/resources/com/ashampoo/kim/updates_jxl/new_gps_coordinates_and_location_shown.jxl new file mode 100644 index 00000000..46e9a398 Binary files /dev/null and b/src/commonTest/resources/com/ashampoo/kim/updates_jxl/new_gps_coordinates_and_location_shown.jxl differ diff --git a/src/commonTest/resources/com/ashampoo/kim/updates_jxl/new_gps_coordinates_and_location_shown.no_metadata.jxl b/src/commonTest/resources/com/ashampoo/kim/updates_jxl/new_gps_coordinates_and_location_shown.no_metadata.jxl new file mode 100644 index 00000000..c265054f Binary files /dev/null and b/src/commonTest/resources/com/ashampoo/kim/updates_jxl/new_gps_coordinates_and_location_shown.no_metadata.jxl differ diff --git a/src/commonTest/resources/com/ashampoo/kim/updates_png/new_gps_coordinates_and_location_shown.no_metadata.png b/src/commonTest/resources/com/ashampoo/kim/updates_png/new_gps_coordinates_and_location_shown.no_metadata.png new file mode 100644 index 00000000..cc36b6f2 Binary files /dev/null and b/src/commonTest/resources/com/ashampoo/kim/updates_png/new_gps_coordinates_and_location_shown.no_metadata.png differ diff --git a/src/commonTest/resources/com/ashampoo/kim/updates_png/new_gps_coordinates_and_location_shown.png b/src/commonTest/resources/com/ashampoo/kim/updates_png/new_gps_coordinates_and_location_shown.png new file mode 100644 index 00000000..1f2f73f3 Binary files /dev/null and b/src/commonTest/resources/com/ashampoo/kim/updates_png/new_gps_coordinates_and_location_shown.png differ diff --git a/src/commonTest/resources/com/ashampoo/kim/updates_webp/new_gps_coordinates_and_location_shown.no_metadata.webp b/src/commonTest/resources/com/ashampoo/kim/updates_webp/new_gps_coordinates_and_location_shown.no_metadata.webp new file mode 100644 index 00000000..993a11ef Binary files /dev/null and b/src/commonTest/resources/com/ashampoo/kim/updates_webp/new_gps_coordinates_and_location_shown.no_metadata.webp differ diff --git a/src/commonTest/resources/com/ashampoo/kim/updates_webp/new_gps_coordinates_and_location_shown.webp b/src/commonTest/resources/com/ashampoo/kim/updates_webp/new_gps_coordinates_and_location_shown.webp new file mode 100644 index 00000000..9dc88873 Binary files /dev/null and b/src/commonTest/resources/com/ashampoo/kim/updates_webp/new_gps_coordinates_and_location_shown.webp differ