From 6800ed28dc5dae04dec24dc6c830f0b31be193c3 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Fri, 24 Mar 2023 13:03:58 +0100 Subject: [PATCH] use a `hash4j` library as it provides well-tested and proven seeded hash implementations GitOrigin-RevId: c43ed1e1037a317b8bcbde33c49f26197748ee02 --- .idea/libraries/hash4j.xml | 12 ++ .../build/CommunityLibraryLicenses.kt | 5 + .../intellij.platform.ide.impl.iml | 1 + .../src/com/intellij/ide/ui/UITheme.java | 22 +-- .../intellij/util/InsecureHashBuilderTest.kt | 8 +- platform/util/intellij.platform.util.ui.iml | 1 + .../com/intellij/openapi/util/IconLoader.kt | 7 +- platform/util/ui/src/com/intellij/ui/hash.kt | 8 + .../src/com/intellij/ui/icons/imageCache.kt | 5 +- .../com/intellij/ui/svg/SvgCacheManager.kt | 15 +- .../util/ui/src/com/intellij/ui/svg/svg.kt | 9 +- .../com/intellij/util/InsecureHashBuilder.kt | 142 ++++-------------- .../ui/src/com/intellij/util/SVGLoader.kt | 22 ++- 13 files changed, 106 insertions(+), 151 deletions(-) create mode 100644 .idea/libraries/hash4j.xml create mode 100644 platform/util/ui/src/com/intellij/ui/hash.kt diff --git a/.idea/libraries/hash4j.xml b/.idea/libraries/hash4j.xml new file mode 100644 index 000000000000..d83828ca7797 --- /dev/null +++ b/.idea/libraries/hash4j.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityLibraryLicenses.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityLibraryLicenses.kt index d527799e3df5..b8d9eeae6edc 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityLibraryLicenses.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityLibraryLicenses.kt @@ -134,6 +134,7 @@ object CommunityLibraryLicenses { LibraryLicense(name = "Apache Commons Discovery", libraryName = "commons-discovery", url = "https://commons.apache.org/dormant/commons-discovery/") .apache("https://commons.apache.org/dormant/commons-discovery/license.html"), + LibraryLicense(name = "Apache Commons HTTPClient", libraryName = "http-client-3.1", version = "3.1  (with patch by JetBrains)", url = "https://hc.apache.org/httpclient-3.x").apache(), LibraryLicense(name = "Apache Commons Imaging (JetBrains's fork)", libraryName = "commons-imaging", @@ -351,6 +352,10 @@ object CommunityLibraryLicenses { .apache("https://raw.githubusercontent.com/google/guava/master/LICENSE"), LibraryLicense(name = "Hamcrest", libraryName = "hamcrest", url = "https://hamcrest.org/") .newBsd("https://github.com/hamcrest/JavaHamcrest/blob/master/LICENSE.txt"), + + LibraryLicense(libraryName = "hash4j", url = "https://github.com/dynatrace-oss/hash4j") + .apache("https://github.com/dynatrace-oss/hash4j/blob/main/LICENSE"), + LibraryLicense(name = "HDR Histogram", libraryName = "HdrHistogram", license = "CC0 1.0 Universal", url = "https://github.com/HdrHistogram/HdrHistogram", licenseUrl = "https://github.com/HdrHistogram/HdrHistogram/blob/master/LICENSE.txt"), diff --git a/platform/platform-impl/intellij.platform.ide.impl.iml b/platform/platform-impl/intellij.platform.ide.impl.iml index cbefd2a95ef5..76a808e2f28e 100644 --- a/platform/platform-impl/intellij.platform.ide.impl.iml +++ b/platform/platform-impl/intellij.platform.ide.impl.iml @@ -143,5 +143,6 @@ + \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/ui/UITheme.java b/platform/platform-impl/src/com/intellij/ide/ui/UITheme.java index e11fa9202030..2ec6188a783a 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/UITheme.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/UITheme.java @@ -229,23 +229,24 @@ public final class UITheme { initializeNamedColors(theme); PaletteScopeManager paletteScopeManager = new PaletteScopeManager(); - if (theme.iconColorsOnSelection != null && !theme.iconColorsOnSelection.isEmpty()) { - Map colors = new TreeMap<>(); - for (Map.Entry entry : theme.iconColorsOnSelection.entrySet()) { - colors.put(entry.getKey(), entry.getValue().toString()); + Map colorsOnSelection = theme.iconColorsOnSelection; + if (colorsOnSelection != null && !colorsOnSelection.isEmpty()) { + Map colors = new HashMap<>(colorsOnSelection.size()); + Set alphaColors = new HashSet<>(colorsOnSelection.size()); + for (Map.Entry entry : colorsOnSelection.entrySet()) { + String value = entry.getValue().toString(); + colors.put(entry.getKey(), value); + alphaColors.add(value); } - Map alpha = new TreeMap<>(); - colors.forEach((key, value) -> alpha.put(value, 255)); theme.selectionColorPatcher = new SVGLoader.SvgElementColorPatcherProvider() { @Override public @Nullable SvgAttributePatcher attributeForPath(@Nullable String path) { PaletteScope scope = paletteScopeManager.getScopeByPath(path); InsecureHashBuilder hash = new InsecureHashBuilder() .stringMap(colors) - .stringIntMap(alpha) .update(scope == null ? ArrayUtilRt.EMPTY_LONG_ARRAY : scope.digest()); - return SvgKt.newSvgPatcher(hash.build(), colors, alpha); + return SvgKt.newSvgPatcher(hash.build(), colors, color -> alphaColors.contains(color) ? 255 : null); } }; } @@ -318,11 +319,10 @@ public final class UITheme { } theme.colorPatcher = new SVGLoader.SvgElementColorPatcherProvider() { - @Nullable @Override - public SvgAttributePatcher attributeForPath(@Nullable String path) { + public @Nullable SvgAttributePatcher attributeForPath(@Nullable String path) { PaletteScope scope = paletteScopeManager.getScopeByPath(path); - return scope == null ? null : SvgKt.newSvgPatcher(scope.digest(), scope.newPalette, scope.alphas); + return scope == null ? null : SvgKt.newSvgPatcher(scope.digest(), scope.newPalette, scope.alphas::get); } }; } diff --git a/platform/platform-tests/testSrc/com/intellij/util/InsecureHashBuilderTest.kt b/platform/platform-tests/testSrc/com/intellij/util/InsecureHashBuilderTest.kt index 74975cbf9a1b..5d3fa932abd3 100644 --- a/platform/platform-tests/testSrc/com/intellij/util/InsecureHashBuilderTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/util/InsecureHashBuilderTest.kt @@ -9,21 +9,21 @@ class InsecureHashBuilderTest { fun `empty map`() { val builder = InsecureHashBuilder() builder.stringMap(mapOf()) - assertThat(builder.build()).isEqualTo(longArrayOf(5238470482016868669L)) + assertThat(builder.build()).isEqualTo(longArrayOf(204526195655617521L)) } @Test fun `string map`() { val builder = InsecureHashBuilder() builder.stringMap(mapOf("foo" to "bar")) - assertThat(builder.build()).isEqualTo(longArrayOf(-2665511616818817272L, -4484417689633454546L, -3459221722170984053L)) + assertThat(builder.build()).isEqualTo(longArrayOf(5110489462189080232L)) } @Test fun `string int map`() { val builder = InsecureHashBuilder() builder.stringIntMap(mapOf("foo" to 123, "bar" to 543)) - assertThat(builder.build()).isEqualTo(longArrayOf(8139571457004014537L, 3354812099852591849L, 3303764292165953682L)) + assertThat(builder.build()).isEqualTo(longArrayOf(8624971787684361546L)) } @Test @@ -31,6 +31,6 @@ class InsecureHashBuilderTest { val builder = InsecureHashBuilder() builder.stringMap(mapOf("foo" to "bar")) builder.stringIntMap(mapOf("foo" to 123, "bar" to 543)) - assertThat(builder.build()).isEqualTo(longArrayOf(-2665511616818817272L, -4484417689633454546L, -3459221722170984053L, 8139571457004014537L, 3354812099852591849L, 3303764292165953682L)) + assertThat(builder.build()).isEqualTo(longArrayOf(5110489462189080232L, 8624971787684361546L)) } } \ No newline at end of file diff --git a/platform/util/intellij.platform.util.ui.iml b/platform/util/intellij.platform.util.ui.iml index 49f40dacbd75..69b3a658a71d 100644 --- a/platform/util/intellij.platform.util.ui.iml +++ b/platform/util/intellij.platform.util.ui.iml @@ -25,5 +25,6 @@ + \ No newline at end of file diff --git a/platform/util/ui/src/com/intellij/openapi/util/IconLoader.kt b/platform/util/ui/src/com/intellij/openapi/util/IconLoader.kt index e474c6702334..5a41d3ff7e06 100644 --- a/platform/util/ui/src/com/intellij/openapi/util/IconLoader.kt +++ b/platform/util/ui/src/com/intellij/openapi/util/IconLoader.kt @@ -24,7 +24,6 @@ import com.intellij.util.ui.* import org.jetbrains.annotations.ApiStatus.Internal import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.TestOnly -import org.jetbrains.xxh3.Xxh3 import java.awt.* import java.awt.image.BufferedImage import java.awt.image.ImageFilter @@ -295,7 +294,9 @@ object IconLoader { } @Internal - fun patchColorsInCacheImageIcon(imageIcon: com.intellij.ui.icons.CachedImageIcon, colorPatcher: SvgElementColorPatcherProvider, isDark: Boolean?): Icon { + fun patchColorsInCacheImageIcon(imageIcon: com.intellij.ui.icons.CachedImageIcon, + colorPatcher: SvgElementColorPatcherProvider, + isDark: Boolean?): Icon { var result = imageIcon if (isDark != null) { val variant = result.getDarkIcon(isDark) @@ -309,7 +310,7 @@ object IconLoader { @Suppress("DEPRECATION") val bytes = colorPatcher.wholeDigest() if (bytes != null) { - digest = longArrayOf(Xxh3.hash(bytes), Xxh3.seededHash(bytes, 5238470482016868669L)) + digest = longArrayOf(hasher.hashBytesToLong(bytes), seededHasher.hashBytesToLong(bytes)) } } diff --git a/platform/util/ui/src/com/intellij/ui/hash.kt b/platform/util/ui/src/com/intellij/ui/hash.kt new file mode 100644 index 000000000000..a3a102fd2063 --- /dev/null +++ b/platform/util/ui/src/com/intellij/ui/hash.kt @@ -0,0 +1,8 @@ +// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.ui + +import com.dynatrace.hash4j.hashing.Hasher64 +import com.dynatrace.hash4j.hashing.Hashing + +internal val hasher: Hasher64 = Hashing.komihash4_3() +internal val seededHasher: Hasher64 = Hashing.komihash4_3(4812324275) \ No newline at end of file diff --git a/platform/util/ui/src/com/intellij/ui/icons/imageCache.kt b/platform/util/ui/src/com/intellij/ui/icons/imageCache.kt index 3cecee58439f..6b8ea2867d9d 100644 --- a/platform/util/ui/src/com/intellij/ui/icons/imageCache.kt +++ b/platform/util/ui/src/com/intellij/ui/icons/imageCache.kt @@ -7,8 +7,10 @@ import com.github.benmanes.caffeine.cache.Weigher import com.intellij.diagnostic.StartUpMeasurer import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.logger +import com.intellij.ui.hasher import com.intellij.ui.scale.DerivedScaleType import com.intellij.ui.scale.ScaleContext +import com.intellij.ui.seededHasher import com.intellij.ui.svg.SvgAttributePatcher import com.intellij.ui.svg.loadSvgAndCacheIfApplicable import com.intellij.util.ArrayUtilRt @@ -18,7 +20,6 @@ import com.intellij.util.io.URLUtil import com.intellij.util.ui.StartupUiUtil import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.TestOnly -import org.jetbrains.xxh3.Xxh3 import java.awt.Image import java.awt.image.BufferedImage import java.awt.image.ImageFilter @@ -216,7 +217,7 @@ private fun loadByDescriptor(rawPathWithoutExt: String, @Suppress("DEPRECATION") deprecatedColorPatcher = colorPatcherProvider.forPath(path) if (deprecatedColorPatcher != null) { - digest = deprecatedColorPatcher.digest()?.let { longArrayOf(Xxh3.hash(it), Xxh3.seededHash(it, 4849512324275L)) } + digest = deprecatedColorPatcher.digest()?.let { longArrayOf(hasher.hashBytesToLong(it), seededHasher.hashBytesToLong(it)) } if (digest == null) { tmpPatcher = true } diff --git a/platform/util/ui/src/com/intellij/ui/svg/SvgCacheManager.kt b/platform/util/ui/src/com/intellij/ui/svg/SvgCacheManager.kt index f0ac44696811..4880bbe8d15f 100644 --- a/platform/util/ui/src/com/intellij/ui/svg/SvgCacheManager.kt +++ b/platform/util/ui/src/com/intellij/ui/svg/SvgCacheManager.kt @@ -6,12 +6,13 @@ package com.intellij.ui.svg import com.intellij.diagnostic.StartUpMeasurer import com.intellij.openapi.application.PathManager import com.intellij.openapi.diagnostic.logger +import com.intellij.ui.hasher import com.intellij.ui.icons.IconLoadMeasurer +import com.intellij.ui.seededHasher import com.intellij.util.SVGLoader import org.intellij.lang.annotations.Language import org.jetbrains.annotations.ApiStatus import org.jetbrains.sqlite.* -import org.jetbrains.xxh3.Xxh3 import sun.awt.image.SunWritableRaster import java.awt.Point import java.awt.Transparency @@ -33,7 +34,7 @@ value class SvgCacheClassifier(internal val key: Int) { constructor(scale: Float, size: Int) : this((scale + (10_000 + size)).toBits()) } -fun getSvgIconCacheFile(): Path = Path.of(PathManager.getSystemPath(), "icon-v8.db") +fun getSvgIconCacheFile(): Path = Path.of(PathManager.getSystemPath(), "icon-v9.db") fun getSvgIconCacheInvalidMarkerFile(file: Path): Path = file.parent.resolve("${file.fileName}.invalidated") @@ -128,8 +129,8 @@ class SvgCacheManager(dbFile: Path) { val start = StartUpMeasurer.getCurrentTimeIfEnabled() return selectStatementPool.use { statement, binder -> val kind = compoundKey.key.toLong() - binder.bind(v1 = Xxh3.hash(imageBytes), v2 = Xxh3.seededHash(imageBytes, SEED), v3 = kind, v4 = themeKey) + binder.bind(v1 = hasher.hashBytesToLong(imageBytes), v2 = seededHasher.hashBytesToLong(imageBytes), v3 = kind, v4 = themeKey) val result = readImage(statement) IconLoadMeasurer.svgCacheRead.end(start) result @@ -143,8 +144,8 @@ class SvgCacheManager(dbFile: Path) { image: BufferedImage) { val data = writeImage(image) if (precomputedCacheKey == 0) { - val key1 = Xxh3.hash(imageBytes) - val key2 = Xxh3.seededHash(imageBytes, SEED) + val key1 = hasher.hashBytesToLong(imageBytes) + val key2 = seededHasher.hashBytesToLong(imageBytes) insertStatementPool.use { statement, binder -> binder.bind(v1 = key1, v2 = key2, @@ -170,7 +171,7 @@ internal fun themeDigestToCacheKey(themeDigest: LongArray): Long { return when (themeDigest.size) { 0 -> 0 1 -> themeDigest.first() - else -> Xxh3.hashLongs(themeDigest) + else -> hasher.hashStream().putLongArray(themeDigest).asLong } } @@ -187,8 +188,6 @@ private val colorModel = ComponentColorModel( private val ZERO_POINT = Point(0, 0) -private const val SEED = 4812324275L - private fun writeImage(image: BufferedImage): ByteArray { val w = image.width val h = image.height diff --git a/platform/util/ui/src/com/intellij/ui/svg/svg.kt b/platform/util/ui/src/com/intellij/ui/svg/svg.kt index 9c546e11e634..4878c563337b 100644 --- a/platform/util/ui/src/com/intellij/ui/svg/svg.kt +++ b/platform/util/ui/src/com/intellij/ui/svg/svg.kt @@ -6,6 +6,7 @@ package com.intellij.ui.svg import com.intellij.diagnostic.StartUpMeasurer import com.intellij.openapi.diagnostic.logger import com.intellij.ui.ColorUtil +import com.intellij.ui.hasher import com.intellij.ui.icons.IconLoadMeasurer import com.intellij.ui.icons.getResourceData import com.intellij.ui.scale.JBUIScale @@ -15,7 +16,6 @@ import com.intellij.util.SVGLoader import com.intellij.util.text.CharSequenceReader import com.intellij.util.xml.dom.createXmlStreamReader import org.jetbrains.annotations.ApiStatus -import org.jetbrains.xxh3.Xxh3 import java.awt.Image import java.awt.geom.Rectangle2D import java.awt.image.BufferedImage @@ -79,7 +79,8 @@ internal fun loadSvg(path: String?, }!! } -fun newSvgPatcher(digest: LongArray?, newPalette: Map, alphas: Map): SvgAttributePatcher? { +@ApiStatus.Internal +fun newSvgPatcher(digest: LongArray?, newPalette: Map, alphaProvider: (String) -> Int?): SvgAttributePatcher? { if (newPalette.isEmpty()) { return null } @@ -114,7 +115,7 @@ fun newSvgPatcher(digest: LongArray?, newPalette: Map, alphas: M } if (newColor != null) { attributes.put(attributeName, newColor) - alphas.get(newColor)?.let { + alphaProvider(newColor)?.let { attributes.put("$attributeName-opacity", (it.toFloat() / 255f).toString()) } } @@ -193,7 +194,7 @@ private inline fun loadAndCacheIfApplicable(path: String?, colorPatcher: SvgAttributePatcher?, deprecatedColorPatcher: SVGLoader.SvgElementColorPatcher?, dataProvider: () -> ByteArray?): BufferedImage? { - val colorPatcherDigest = colorPatcher?.digest() ?: deprecatedColorPatcher?.digest()?.let { longArrayOf(Xxh3.hash(it)) } + val colorPatcherDigest = colorPatcher?.digest() ?: deprecatedColorPatcher?.digest()?.let { longArrayOf(hasher.hashBytesToLong(it)) } val svgCache = svgCache if (svgCache == null || !svgCache.isActive() || (colorPatcherDigest == null && (colorPatcher != null || deprecatedColorPatcher != null)) || diff --git a/platform/util/ui/src/com/intellij/util/InsecureHashBuilder.kt b/platform/util/ui/src/com/intellij/util/InsecureHashBuilder.kt index 0d07214da3d9..9fc61d47fa39 100644 --- a/platform/util/ui/src/com/intellij/util/InsecureHashBuilder.kt +++ b/platform/util/ui/src/com/intellij/util/InsecureHashBuilder.kt @@ -3,139 +3,59 @@ package com.intellij.util +import com.dynatrace.hash4j.hashing.HashFunnel +import com.intellij.ui.hasher import it.unimi.dsi.fastutil.longs.LongArrayList import org.jetbrains.annotations.ApiStatus.Experimental -import org.jetbrains.xxh3.Access -import org.jetbrains.xxh3.Xxh3 -import org.jetbrains.xxh3.Xxh3Impl -import java.util.* -private const val extraSeed = 1867412186L - -// see https://github.com/Cyan4973/xxHash/wiki/Collision-ratio-comparison @Experimental class InsecureHashBuilder { private val result = LongArrayList() + private val hashStream = hasher.hashStream() fun build(): LongArray { return result.toLongArray() } - fun update(value: CharSequence) { - result.add(Xxh3.hashUnencodedChars(value)) + fun update(value: CharSequence): InsecureHashBuilder { + hashStream.putChars(value) + return addAndReset() + } + + private fun addAndReset(): InsecureHashBuilder { + result.add(hashStream.asLong) + hashStream.reset() + return this } fun update(value: LongArray): InsecureHashBuilder { - result.add(Xxh3.hashLongs(value)) - return this + hashStream.putLongArray(value) + return addAndReset() } - fun update(value: Int) { - result.add(Xxh3.hashInt(value)) + fun update(value: Int): InsecureHashBuilder { + hashStream.putInt(value) + return addAndReset() } - fun updateRaw(hash: Long) { - result.add(hash) - } - - fun stringList(list: List) { - val size = list.size - result.add(Xxh3.hashInt(size)) - - if (list.isEmpty()) { - return - } - - val offset = result.size - result.ensureCapacity(size) - for (s in list) { - result.add(Xxh3.hashUnencodedChars(s)) - } - hash128(offset) + fun stringList(list: List): InsecureHashBuilder { + hashStream.putOrderedIterable(list, HashFunnel.forString()) + return addAndReset() } fun stringMap(map: Map): InsecureHashBuilder { - val size = map.size - result.add(Xxh3.hashInt(size)) + hashStream.putUnorderedIterable(map.entries, HashFunnel.forEntry( + HashFunnel.forString(), + HashFunnel.forString()), hasher) + return addAndReset() + } - if (map.isEmpty()) { - return this - } - - val offset = result.size - result.ensureCapacity(size * 2) - - if (map is SortedMap || map is LinkedHashMap) { - for ((k, v) in map) { - result.add(Xxh3.hashUnencodedChars(k)) - result.add(Xxh3.hashUnencodedChars(v)) - } - } - else { - for (k in map.keys.sorted()) { - result.add(Xxh3.hashUnencodedChars(k)) - val v = map.get(k) - result.add(if (v == null) 0 else Xxh3.hashUnencodedChars(v)) - } - } - hash128(offset) + fun stringIntMap(map: Map): InsecureHashBuilder { + hashStream.putUnorderedIterable(map.entries, HashFunnel.forEntry( + HashFunnel.forString(), + HashFunnel { v, sink -> sink.putInt(v) } + ), hasher) + addAndReset() return this } - - fun stringIntMap(map: Map): InsecureHashBuilder { - val size = map.size - result.add(Xxh3.hashInt(size)) - - if (map.isEmpty()) { - return this - } - - val offset = result.size - result.ensureCapacity(size * 2) - - if (map is SortedMap || map is LinkedHashMap) { - for ((k, v) in map) { - result.add(Xxh3.hashUnencodedChars(k)) - result.add(if (v == null) 0 else Xxh3.hashInt(v, 0)) - } - } - else { - for (k in map.keys.sorted()) { - result.add(Xxh3.hashUnencodedChars(k)) - val v = map.get(k) - result.add(if (v == null) 0 else Xxh3.hashInt(v, 0)) - } - } - - hash128(offset) - return this - } - - private fun hash128(offset: Int) { - val sizeInBytes = (result.size - offset) * Long.SIZE_BYTES - val offsetInBytes = offset * Long.SIZE_BYTES - val l1 = Xxh3Impl.hash(result, LongListAccessForLongs, offsetInBytes, sizeInBytes, 0) - // and with a custom seed to get a 128-bit hash - val l2 = Xxh3Impl.hash(result, LongListAccessForLongs, offsetInBytes, sizeInBytes, extraSeed) - result.size(offset) - result.add(l1) - result.add(l2) - } } - -// special implementation for hashing long array - it is guaranteed that only i64 will be called (as input is aligned) -object LongListAccessForLongs : Access { - override fun i64(input: LongArrayList, offset: Int): Long { - return input.getLong(offset shr 3) - } - - override fun i32(input: LongArrayList, offset: Int): Int { - val v = input.getLong(offset shr 3) - return if (offset and 7 == 0) (v shr 32).toInt() else v.toInt() - } - - override fun i8(input: LongArrayList, offset: Int): Int { - throw UnsupportedOperationException() - } -} - diff --git a/platform/util/ui/src/com/intellij/util/SVGLoader.kt b/platform/util/ui/src/com/intellij/util/SVGLoader.kt index bdd256d7e5df..ccaa6614f1f5 100644 --- a/platform/util/ui/src/com/intellij/util/SVGLoader.kt +++ b/platform/util/ui/src/com/intellij/util/SVGLoader.kt @@ -10,6 +10,7 @@ import com.intellij.ui.scale.DerivedScaleType import com.intellij.ui.scale.ScaleContext import com.intellij.ui.svg.* import com.intellij.util.ui.ImageUtil +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import org.jetbrains.annotations.ApiStatus import org.w3c.dom.Element import java.awt.* @@ -101,20 +102,25 @@ object SVGLoader { backgroundColors: List = emptyList()): SvgElementColorPatcherProvider { val fg = ColorUtil.toHtmlColor(resultColor) val map = strokeColors.associateWith { fg } - val alpha = HashMap(map.size) + val alpha = Object2IntOpenHashMap(map.size) + alpha.defaultReturnValue(Int.MIN_VALUE) for (s in map.values) { alpha.put(s, resultColor.alpha) } - val hash = InsecureHashBuilder() - hash.stringList(strokeColors) - hash.stringList(backgroundColors) - hash.update(fg) - hash.update(resultColor.alpha) - val digest = hash.build() + val digest = InsecureHashBuilder() + .stringList(strokeColors) + .stringList(backgroundColors) + .update(fg) + .update(resultColor.alpha) + .build() return object : SvgElementColorPatcherProvider { override fun attributeForPath(path: String?): SvgAttributePatcher? { - return newSvgPatcher(digest = digest, newPalette = map + backgroundColors.associateWith { "#00000000" }, alphas = alpha) + return newSvgPatcher(digest = digest, + newPalette = map + backgroundColors.associateWith { "#00000000" }, + alphaProvider = { color -> + alpha.getInt(color).takeIf { it != Int.MIN_VALUE } + }) } override fun digest() = digest