[duplicates] enable duplicates analysis in PyCharm/WebStorm/PhpStorm/RubyMine
[idea/community.git] / platform / core-api / src / com / intellij / util / IconUtil.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.util;
3
4 import com.intellij.icons.AllIcons;
5 import com.intellij.ide.FileIconPatcher;
6 import com.intellij.ide.FileIconProvider;
7 import com.intellij.ide.TypePresentationService;
8 import com.intellij.openapi.fileTypes.DirectoryFileType;
9 import com.intellij.openapi.fileTypes.FileType;
10 import com.intellij.openapi.project.DumbService;
11 import com.intellij.openapi.project.Project;
12 import com.intellij.openapi.util.*;
13 import com.intellij.openapi.vfs.VFileProperty;
14 import com.intellij.openapi.vfs.VirtualFile;
15 import com.intellij.openapi.vfs.WritingAccessProvider;
16 import com.intellij.ui.*;
17 import com.intellij.util.ui.*;
18 import com.intellij.util.ui.JBUIScale.ScaleContext;
19 import com.intellij.util.ui.JBUIScale.ScaleContextAware;
20 import org.jetbrains.annotations.Contract;
21 import org.jetbrains.annotations.NotNull;
22 import org.jetbrains.annotations.Nullable;
23
24 import javax.swing.*;
25 import java.awt.*;
26 import java.awt.geom.AffineTransform;
27 import java.awt.image.BufferedImage;
28 import java.awt.image.RGBImageFilter;
29 import java.lang.ref.WeakReference;
30 import java.util.List;
31 import java.util.Objects;
32 import java.util.function.Supplier;
33
34 import static com.intellij.util.ui.JBUIScale.ScaleType.OBJ_SCALE;
35 import static com.intellij.util.ui.JBUIScale.ScaleType.USR_SCALE;
36
37
38 /**
39  * @author max
40  * @author Konstantin Bulenkov
41  */
42 public class IconUtil {
43   private static final Key<Boolean> PROJECT_WAS_EVER_INITIALIZED = Key.create("iconDeferrer:projectWasEverInitialized");
44
45   private static boolean wasEverInitialized(@NotNull Project project) {
46     Boolean was = project.getUserData(PROJECT_WAS_EVER_INITIALIZED);
47     if (was == null) {
48       if (project.isInitialized()) {
49         was = true;
50         project.putUserData(PROJECT_WAS_EVER_INITIALIZED, true);
51       }
52       else {
53         was = false;
54       }
55     }
56
57     return was.booleanValue();
58   }
59
60   @NotNull
61   public static Icon cropIcon(@NotNull Icon icon, int maxWidth, int maxHeight) {
62     if (icon.getIconHeight() <= maxHeight && icon.getIconWidth() <= maxWidth) {
63       return icon;
64     }
65
66     Image image = toImage(icon);
67     if (image == null) return icon;
68
69     double scale = 1f;
70     if (image instanceof JBHiDPIScaledImage) {
71       scale = ((JBHiDPIScaledImage)image).getScale();
72       image = ((JBHiDPIScaledImage)image).getDelegate();
73     }
74     BufferedImage bi = ImageUtil.toBufferedImage(image);
75     final Graphics2D g = bi.createGraphics();
76
77     int imageWidth = ImageUtil.getRealWidth(image);
78     int imageHeight = ImageUtil.getRealHeight(image);
79
80     maxWidth = maxWidth == Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)Math.round(maxWidth * scale);
81     maxHeight = maxHeight == Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)Math.round(maxHeight * scale);
82     final int w = Math.min(imageWidth, maxWidth);
83     final int h = Math.min(imageHeight, maxHeight);
84
85     final BufferedImage img = UIUtil.createImage(g, w, h, Transparency.TRANSLUCENT);
86     final int offX = imageWidth > maxWidth ? (imageWidth - maxWidth) / 2 : 0;
87     final int offY = imageHeight > maxHeight ? (imageHeight - maxHeight) / 2 : 0;
88     for (int col = 0; col < w; col++) {
89       for (int row = 0; row < h; row++) {
90         img.setRGB(col, row, bi.getRGB(col + offX, row + offY));
91       }
92     }
93     g.dispose();
94     return new JBImageIcon(RetinaImage.createFrom(img, scale, null));
95   }
96
97   @NotNull
98   public static Icon cropIcon(@NotNull Icon icon, @NotNull Rectangle area) {
99     if (!new Rectangle(icon.getIconWidth(), icon.getIconHeight()).contains(area)) {
100       return icon;
101     }
102     return new CropIcon(icon, area);
103   }
104
105   @NotNull
106   public static Icon flip(@NotNull final Icon icon, final boolean horizontal) {
107     return new Icon() {
108       @Override
109       public void paintIcon(Component c, Graphics g, int x, int y) {
110         Graphics2D g2d = (Graphics2D)g.create();
111         try {
112           AffineTransform transform =
113             AffineTransform.getTranslateInstance(horizontal ? x + getIconWidth() : x, horizontal ? y : y + getIconHeight());
114           transform.concatenate(AffineTransform.getScaleInstance(horizontal ? -1 : 1, horizontal ? 1 : -1));
115           transform.preConcatenate(g2d.getTransform());
116           g2d.setTransform(transform);
117           icon.paintIcon(c, g2d, 0, 0);
118         }
119         finally {
120           g2d.dispose();
121         }
122       }
123
124       @Override
125       public int getIconWidth() {
126         return icon.getIconWidth();
127       }
128
129       @Override
130       public int getIconHeight() {
131         return icon.getIconHeight();
132       }
133     };
134   }
135
136   private static final NullableFunction<FileIconKey, Icon> ICON_NULLABLE_FUNCTION = key -> {
137     VirtualFile file = key.getFile();
138     int flags = filterFileIconFlags(file, key.getFlags());
139     Project project = key.getProject();
140
141     if (!file.isValid() || project != null && (project.isDisposed() || !wasEverInitialized(project))) return null;
142
143     Icon providersIcon = getProvidersIcon(file, flags, project);
144     Icon icon = providersIcon != null ? providersIcon : getBaseIcon(file);
145
146     boolean dumb = project != null && DumbService.getInstance(project).isDumb();
147     for (FileIconPatcher patcher : getPatchers()) {
148       if (dumb && !DumbService.isDumbAware(patcher)) {
149         continue;
150       }
151
152       // render without locked icon patch since we are going to apply it later anyway
153       icon = patcher.patchIcon(icon, file, flags & ~Iconable.ICON_FLAG_READ_STATUS, project);
154     }
155
156     if (file.is(VFileProperty.SYMLINK)) {
157       icon = new LayeredIcon(icon, PlatformIcons.SYMLINK_ICON);
158     }
159     if (BitUtil.isSet(flags, Iconable.ICON_FLAG_READ_STATUS) &&
160         (!file.isWritable() || !WritingAccessProvider.isPotentiallyWritable(file, project))) {
161       icon = new LayeredIcon(icon, PlatformIcons.LOCKED_ICON);
162     }
163
164     Iconable.LastComputedIcon.put(file, icon, flags);
165
166     return icon;
167   };
168
169   @Iconable.IconFlags
170   private static int filterFileIconFlags(VirtualFile file, @Iconable.IconFlags int flags) {
171     UserDataHolder fileTypeDataHolder = ObjectUtils.tryCast(file.getFileType(), UserDataHolder.class);
172     int fileTypeFlagIgnoreMask = Iconable.ICON_FLAG_IGNORE_MASK.get(fileTypeDataHolder, 0);
173     int flagIgnoreMask = Iconable.ICON_FLAG_IGNORE_MASK.get(file, fileTypeFlagIgnoreMask);
174     //noinspection MagicConstant
175     return flags & ~flagIgnoreMask;
176   }
177
178   public static Icon getIcon(@NotNull VirtualFile file, @Iconable.IconFlags int flags, @Nullable Project project) {
179     Icon lastIcon = Iconable.LastComputedIcon.get(file, flags);
180     Icon base = lastIcon != null ? lastIcon : getBaseIcon(file);
181     return IconDeferrer.getInstance().defer(base, new FileIconKey(file, project, flags), ICON_NULLABLE_FUNCTION);
182   }
183
184   private static Icon getBaseIcon(VirtualFile vFile) {
185     Icon icon = TypePresentationService.getService().getIcon(vFile);
186     if (icon != null) {
187       return icon;
188     }
189     FileType fileType = vFile.getFileType();
190     if (vFile.isDirectory() && !(fileType instanceof DirectoryFileType)) {
191       return PlatformIcons.FOLDER_ICON;
192     }
193     return fileType.getIcon();
194   }
195
196   @Nullable
197   private static Icon getProvidersIcon(@NotNull VirtualFile file, @Iconable.IconFlags int flags, Project project) {
198     for (FileIconProvider provider : getProviders()) {
199       final Icon icon = provider.getIcon(file, flags, project);
200       if (icon != null) return icon;
201     }
202     return null;
203   }
204
205   @NotNull
206   public static Icon getEmptyIcon(boolean showVisibility) {
207     RowIcon baseIcon = new RowIcon(2);
208     baseIcon.setIcon(EmptyIcon.create(PlatformIcons.CLASS_ICON), 0);
209     if (showVisibility) {
210       baseIcon.setIcon(EmptyIcon.create(PlatformIcons.PUBLIC_ICON), 1);
211     }
212     return baseIcon;
213   }
214
215   private static class FileIconProviderHolder {
216     private static final List<FileIconProvider> myProviders = FileIconProvider.EP_NAME.getExtensionList();
217   }
218
219   @NotNull
220   private static List<FileIconProvider> getProviders() {
221     return FileIconProviderHolder.myProviders;
222   }
223
224   private static class FileIconPatcherHolder {
225     private static final List<FileIconPatcher> ourPatchers = FileIconPatcher.EP_NAME.getExtensionList();
226   }
227
228   @NotNull
229   private static List<FileIconPatcher> getPatchers() {
230     return FileIconPatcherHolder.ourPatchers;
231   }
232
233   public static Image toImage(@NotNull Icon icon) {
234     return toImage(icon, null);
235   }
236
237   public static Image toImage(@NotNull Icon icon, @Nullable ScaleContext ctx) {
238     return IconLoader.toImage(icon, ctx);
239   }
240
241   @NotNull
242   public static Icon getAddIcon() {
243     return AllIcons.General.Add;
244   }
245
246   @NotNull
247   public static Icon getRemoveIcon() {
248     return AllIcons.General.Remove;
249   }
250
251   @NotNull
252   public static Icon getMoveUpIcon() {
253     return AllIcons.Actions.MoveUp;
254   }
255
256   @NotNull
257   public static Icon getMoveDownIcon() {
258     return AllIcons.Actions.MoveDown;
259   }
260
261   @NotNull
262   public static Icon getEditIcon() {
263     return AllIcons.Actions.Edit;
264   }
265
266   @NotNull
267   public static Icon getAddClassIcon() {
268     return AllIcons.ToolbarDecorator.AddClass;
269   }
270
271   @NotNull
272   public static Icon getAddPatternIcon() {
273     return AllIcons.ToolbarDecorator.AddPattern;
274   }
275
276   @NotNull
277   public static Icon getAddJiraPatternIcon() {
278     return AllIcons.ToolbarDecorator.AddJira;
279   }
280
281   @NotNull
282   public static Icon getAddYouTrackPatternIcon() {
283     return AllIcons.ToolbarDecorator.AddYouTrack;
284   }
285
286   @NotNull
287   public static Icon getAddBlankLineIcon() {
288     return AllIcons.ToolbarDecorator.AddBlankLine;
289   }
290
291   @NotNull
292   public static Icon getAddPackageIcon() {
293     return AllIcons.ToolbarDecorator.AddFolder;
294   }
295
296   @NotNull
297   public static Icon getAddLinkIcon() {
298     return AllIcons.ToolbarDecorator.AddLink;
299   }
300
301   @NotNull
302   public static Icon getAddFolderIcon() {
303     return AllIcons.ToolbarDecorator.AddFolder;
304   }
305
306   @NotNull
307   public static Icon getAnalyzeIcon() {
308     return getToolbarDecoratorIcon("analyze.png");
309   }
310
311   public static void paintInCenterOf(@NotNull Component c, @NotNull Graphics g, @NotNull Icon icon) {
312     final int x = (c.getWidth() - icon.getIconWidth()) / 2;
313     final int y = (c.getHeight() - icon.getIconHeight()) / 2;
314     icon.paintIcon(c, g, x, y);
315   }
316
317   @NotNull
318   private static Icon getToolbarDecoratorIcon(@NotNull String name) {
319     return IconLoader.getIcon(getToolbarDecoratorIconsFolder() + name);
320   }
321
322   @NotNull
323   private static String getToolbarDecoratorIconsFolder() {
324     return "/toolbarDecorator/" + (SystemInfo.isMac ? "mac/" : "");
325   }
326
327   /**
328    * Result icons look like original but have equal (maximum) size
329    */
330   @NotNull
331   public static Icon[] getEqualSizedIcons(@NotNull Icon... icons) {
332     Icon[] result = new Icon[icons.length];
333     int width = 0;
334     int height = 0;
335     for (Icon icon : icons) {
336       width = Math.max(width, icon.getIconWidth());
337       height = Math.max(height, icon.getIconHeight());
338     }
339     for (int i = 0; i < icons.length; i++) {
340       result[i] = new IconSizeWrapper(icons[i], width, height);
341     }
342     return result;
343   }
344
345   @NotNull
346   public static Icon toSize(@Nullable Icon icon, int width, int height) {
347     return new IconSizeWrapper(icon, width, height);
348   }
349
350   public static class IconSizeWrapper implements Icon {
351     private final Icon myIcon;
352     private final int myWidth;
353     private final int myHeight;
354
355     protected IconSizeWrapper(@Nullable Icon icon, int width, int height) {
356       myIcon = icon;
357       myWidth = width;
358       myHeight = height;
359     }
360
361     @Override
362     public void paintIcon(Component c, Graphics g, int x, int y) {
363       paintIcon(myIcon, c, g, x, y);
364     }
365
366     protected void paintIcon(@Nullable Icon icon, Component c, Graphics g, int x, int y) {
367       if (icon == null) return;
368       x += (myWidth - icon.getIconWidth()) / 2;
369       y += (myHeight - icon.getIconHeight()) / 2;
370       icon.paintIcon(c, g, x, y);
371     }
372
373     @Override
374     public int getIconWidth() {
375       return myWidth;
376     }
377
378     @Override
379     public int getIconHeight() {
380       return myHeight;
381     }
382   }
383
384   private static class CropIcon implements Icon {
385     private final Icon mySrc;
386     private final Rectangle myCrop;
387
388     private CropIcon(@NotNull Icon src, @NotNull Rectangle crop) {
389       mySrc = src;
390       myCrop = crop;
391     }
392
393     @Override
394     public void paintIcon(Component c, Graphics g, int x, int y) {
395       mySrc.paintIcon(c, g, x - myCrop.x, y - myCrop.y);
396     }
397
398     @Override
399     public int getIconWidth() {
400       return myCrop.width;
401     }
402
403     @Override
404     public int getIconHeight() {
405       return myCrop.height;
406     }
407   }
408
409   /**
410    * @deprecated use {@link #scale(Icon, Component, float)}
411    */
412   @Deprecated
413   @NotNull
414   public static Icon scale(@NotNull final Icon source, double _scale) {
415     final double scale = Math.min(32, Math.max(.1, _scale));
416     return new Icon() {
417       @Override
418       public void paintIcon(Component c, Graphics g, int x, int y) {
419         Graphics2D g2d = (Graphics2D)g.create();
420         try {
421           g2d.translate(x, y);
422           AffineTransform transform = AffineTransform.getScaleInstance(scale, scale);
423           transform.preConcatenate(g2d.getTransform());
424           g2d.setTransform(transform);
425           g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
426           source.paintIcon(c, g2d, 0, 0);
427         }
428         finally {
429           g2d.dispose();
430         }
431       }
432
433       @Override
434       public int getIconWidth() {
435         return (int)(source.getIconWidth() * scale);
436       }
437
438       @Override
439       public int getIconHeight() {
440         return (int)(source.getIconHeight() * scale);
441       }
442     };
443   }
444
445   /**
446    * Returns a copy of the provided {@code icon}.
447    *
448    * @see CopyableIcon
449    */
450   @Contract("null, _->null; !null, _->!null")
451   public static Icon copy(@Nullable Icon icon, @Nullable Component ancestor) {
452     return IconLoader.copy(icon, ancestor, false);
453   }
454
455   /**
456    * Returns a deep copy of the provided {@code icon}.
457    *
458    * @see CopyableIcon
459    */
460   @Contract("null, _->null; !null, _->!null")
461   public static Icon deepCopy(@Nullable Icon icon, @Nullable Component ancestor) {
462     return IconLoader.copy(icon, ancestor, true);
463   }
464
465   /**
466    * Returns a scaled icon instance.
467    * <p>
468    * The method delegates to {@link ScalableIcon#scale(float)} when applicable,
469    * otherwise defaults to {@link #scale(Icon, double)}
470    * <p>
471    * In the following example:
472    * <pre>
473    * Icon myIcon = new MyIcon();
474    * Icon scaledIcon = IconUtil.scale(myIcon, myComp, 2f);
475    * Icon anotherScaledIcon = IconUtil.scale(scaledIcon, myComp, 2f);
476    * assert(scaledIcon.getIconWidth() == anotherScaledIcon.getIconWidth()); // compare the scale of the icons
477    * </pre>
478    * The result of the assertion depends on {@code MyIcon} implementation. When {@code scaledIcon} is an instance of {@link ScalableIcon},
479    * then {@code anotherScaledIcon} should be scaled according to the {@link ScalableIcon} javadoc, and the assertion should pass.
480    * Otherwise, {@code anotherScaledIcon} should be 2 times bigger than {@code scaledIcon}, and 4 times bigger than {@code myIcon}.
481    * So, prior to scale the icon recursively, the returned icon should be inspected for its type to understand the result.
482    * But recursive scale should better be avoided.
483    *
484    * @param icon the icon to scale
485    * @param ancestor the component (or its ancestor) painting the icon, or null when not available
486    * @param scale the scale factor
487    * @return the scaled icon
488    */
489   @NotNull
490   public static Icon scale(@NotNull Icon icon, @Nullable Component ancestor, float scale) {
491     if (icon instanceof ScalableIcon) {
492       if (icon instanceof ScaleContextAware) {
493         ((ScaleContextAware)icon).updateScaleContext(ancestor != null ? ScaleContext.create(ancestor) : null);
494       }
495       return ((ScalableIcon)icon).scale(scale);
496     }
497     return scale(icon, scale);
498   }
499
500   /**
501    * Returns a scaled icon instance, in scale of the provided font size.
502    * <p>
503    * The method delegates to {@link ScalableIcon#scale(float)} when applicable,
504    * otherwise defaults to {@link #scale(Icon, double)}
505    * <p>
506    * Refer to {@link #scale(Icon, Component, float)} for more details.
507    *
508    * @param icon the icon to scale
509    * @param ancestor the component (or its ancestor) painting the icon, or null when not available
510    * @param fontSize the reference font size
511    * @return the scaled icon
512    */
513   @NotNull
514   public static Icon scaleByFont(@NotNull Icon icon, @Nullable Component ancestor, float fontSize) {
515     float scale = JBUI.getFontScale(fontSize);
516     if (icon instanceof ScaleContextAware) {
517       ScaleContextAware ctxIcon = (ScaleContextAware)icon;
518       // take into account the user scale of the icon
519       double usrScale = ctxIcon.getScaleContext().getScale(USR_SCALE);
520       scale /= usrScale;
521     }
522     return scale(icon, ancestor, scale);
523   }
524
525   /**
526    * Overrides the provided scale in the icon's scale context and in the composited icon's scale contexts (when applicable).
527    *
528    * @see JBUIScale.UserScaleContext#overrideScale(JBUIScale.Scale)
529    */
530   @NotNull
531   public static Icon overrideScale(@NotNull Icon icon, JBUIScale.Scale scale) {
532     if (icon instanceof CompositeIcon) {
533       CompositeIcon compositeIcon = (CompositeIcon)icon;
534       for (int i = 0; i < compositeIcon.getIconCount(); i++) {
535         Icon subIcon = compositeIcon.getIcon(i);
536         if (subIcon != null) overrideScale(subIcon, scale);
537       }
538     }
539     if (icon instanceof ScaleContextAware) {
540       ((ScaleContextAware)icon).getScaleContext().overrideScale(scale);
541     }
542     return icon;
543   }
544
545   @NotNull
546   public static Icon colorize(@NotNull Icon source, @NotNull Color color) {
547     return colorize(source, color, false);
548   }
549
550   @NotNull
551   public static Icon colorize(Graphics2D g, @NotNull Icon source, @NotNull Color color) {
552     return colorize(g, source, color, false);
553   }
554
555   @NotNull
556   public static Icon colorize(@NotNull Icon source, @NotNull Color color, boolean keepGray) {
557     return filterIcon(null, source, new ColorFilter(color, keepGray));
558   }
559
560   @NotNull
561   public static Icon colorize(Graphics2D g, @NotNull Icon source, @NotNull Color color, boolean keepGray) {
562     return filterIcon(g, source, new ColorFilter(color, keepGray));
563   }
564   @NotNull
565   public static Icon desaturate(@NotNull Icon source) {
566     return filterIcon(null, source, new DesaturationFilter());
567   }
568
569   @NotNull
570   public static Icon brighter(@NotNull Icon source, int tones) {
571     return filterIcon(null, source, new BrighterFilter(tones));
572   }
573
574   @NotNull
575   public static Icon darker(@NotNull Icon source, int tones) {
576     return filterIcon(null, source, new DarkerFilter(tones));
577   }
578
579   @NotNull
580   private static Icon filterIcon(Graphics2D g, @NotNull Icon source, @NotNull Filter filter) {
581     BufferedImage src = g != null ? UIUtil.createImage(g, source.getIconWidth(), source.getIconHeight(), Transparency.TRANSLUCENT) :
582                                     UIUtil.createImage(source.getIconWidth(), source.getIconHeight(), Transparency.TRANSLUCENT);
583     Graphics2D g2d = src.createGraphics();
584     source.paintIcon(null, g2d, 0, 0);
585     g2d.dispose();
586     BufferedImage img = g != null ? UIUtil.createImage(g, source.getIconWidth(), source.getIconHeight(), Transparency.TRANSLUCENT) :
587                                     UIUtil.createImage(source.getIconWidth(), source.getIconHeight(), Transparency.TRANSLUCENT);
588     int[] rgba = new int[4];
589     for (int y = 0; y < src.getRaster().getHeight(); y++) {
590       for (int x = 0; x < src.getRaster().getWidth(); x++) {
591         src.getRaster().getPixel(x, y, rgba);
592         if (rgba[3] != 0) {
593           img.getRaster().setPixel(x, y, filter.convert(rgba));
594         }
595       }
596     }
597     return createImageIcon((Image)img);
598   }
599
600   @FunctionalInterface
601   private interface Filter {
602     @NotNull
603     int[] convert(@NotNull int[] rgba);
604   }
605
606   private static class ColorFilter implements Filter {
607     private final float[] myBase;
608     private final boolean myKeepGray;
609
610     private ColorFilter(@NotNull Color color, boolean keepGray) {
611       myKeepGray = keepGray;
612       myBase = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
613     }
614
615     @NotNull
616     @Override
617     public int[] convert(@NotNull int[] rgba) {
618       float[] hsb = new float[3];
619       Color.RGBtoHSB(rgba[0], rgba[1], rgba[2], hsb);
620       int rgb = Color.HSBtoRGB(myBase[0], myBase[1] * (myKeepGray ? hsb[1] : 1f), myBase[2] * hsb[2]);
621       return new int[]{rgb >> 16 & 0xff, rgb >> 8 & 0xff, rgb & 0xff, rgba[3]};
622     }
623   }
624
625   private static class DesaturationFilter implements Filter {
626     @NotNull
627     @Override
628     public int[] convert(@NotNull int[] rgba) {
629       int min = Math.min(Math.min(rgba[0], rgba[1]), rgba[2]);
630       int max = Math.max(Math.max(rgba[0], rgba[1]), rgba[2]);
631       int grey = (max + min) / 2;
632       return new int[]{grey, grey, grey, rgba[3]};
633     }
634   }
635
636   private static class BrighterFilter implements Filter {
637     private final int myTones;
638
639     BrighterFilter(int tones) {
640       myTones = tones;
641     }
642
643     @NotNull
644     @Override
645     public int[] convert(@NotNull int[] rgba) {
646       Color color = ColorUtil.hackBrightness(rgba[0], rgba[1], rgba[2], myTones, 1.1f);
647       return new int[]{color.getRed(), color.getGreen(), color.getBlue(), rgba[3]};
648     }
649   }
650
651   private static class DarkerFilter implements Filter {
652     private final int myTones;
653
654     DarkerFilter(int tones) {
655       myTones = tones;
656     }
657
658     @NotNull
659     @Override
660     public int[] convert(@NotNull int[] rgba) {
661       Color color = ColorUtil.hackBrightness(rgba[0], rgba[1], rgba[2], myTones, 1/1.1f);
662       return new int[]{color.getRed(), color.getGreen(), color.getBlue(), rgba[3]};
663     }
664   }
665
666   /**
667    * @deprecated Use {@link #createImageIcon(Image)}
668    */
669   @Deprecated
670   @NotNull
671   public static JBImageIcon createImageIcon(@NotNull final BufferedImage img) {
672     return createImageIcon((Image)img);
673   }
674
675   @NotNull
676   public static JBImageIcon createImageIcon(@NotNull final Image img) {
677     return new JBImageIcon(img) {
678       @Override
679       public int getIconWidth() {
680         return ImageUtil.getUserWidth(getImage());
681       }
682
683       @Override
684       public int getIconHeight() {
685         return ImageUtil.getUserHeight(getImage());
686       }
687     };
688   }
689
690   @NotNull
691   public static Icon textToIcon(@NotNull final String text, @NotNull final Component component, final float fontSize) {
692     class MyIcon extends JBScalableIcon {
693       private @NotNull final String myText;
694       private Font myFont;
695       private FontMetrics myMetrics;
696       private final WeakReference<Component> myCompRef = new WeakReference<>(component);
697
698       private MyIcon(@NotNull final String text) {
699         myText = text;
700         setIconPreScaled(false);
701         getScaleContext().addUpdateListener(() -> update());
702         update();
703       }
704
705       @Override
706       public void paintIcon(Component c, Graphics g, int x, int y) { // x,y is in USR_SCALE
707         g = g.create();
708         try {
709           GraphicsUtil.setupAntialiasing(g);
710           g.setFont(myFont);
711           UIUtil.drawStringWithHighlighting(g, myText,
712                                             (int)scaleVal(x, OBJ_SCALE) + (int)scaleVal(2),
713                                             (int)scaleVal(y, OBJ_SCALE) + getIconHeight() - (int)scaleVal(1),
714                                             JBColor.foreground(), JBColor.background());
715         }
716         finally {
717           g.dispose();
718         }
719       }
720
721       @Override
722       public int getIconWidth() {
723         return myMetrics.stringWidth(myText) + (int)scaleVal(4);
724       }
725
726       @Override
727       public int getIconHeight() {
728         return myMetrics.getHeight();
729       }
730
731       private void update() {
732         myFont = JBFont.create(JBUI.Fonts.label().deriveFont((float)scaleVal(fontSize, OBJ_SCALE))); // fontSize is in USR_SCALE
733         Component comp = myCompRef.get();
734         if (comp == null) comp = new Component() {};
735         myMetrics = comp.getFontMetrics(myFont);
736       }
737
738       @Override
739       public boolean equals(Object o) {
740         if (this == o) return true;
741         if (!(o instanceof MyIcon)) return false;
742         final MyIcon icon = (MyIcon)o;
743
744         if (!Objects.equals(myText, icon.myText)) return false;
745         if (!Objects.equals(myFont, icon.myFont)) return false;
746         return true;
747       }
748     }
749
750     return new MyIcon(text);
751   }
752
753   @NotNull
754   public static Icon addText(@NotNull Icon base, @NotNull String text) {
755     LayeredIcon icon = new LayeredIcon(2);
756     icon.setIcon(base, 0);
757     icon.setIcon(textToIcon(text, new JLabel(), JBUI.scale(6f)), 1, SwingConstants.SOUTH_EAST);
758     return icon;
759   }
760
761   /**
762    * Creates new icon with the filter applied.
763    */
764   @Nullable
765   public static Icon filterIcon(@NotNull Icon icon, Supplier<RGBImageFilter> filterSupplier, @Nullable Component ancestor) {
766     return IconLoader.filterIcon(icon, filterSupplier, ancestor);
767   }
768
769   /**
770    * This method works with compound icons like RowIcon or LayeredIcon
771    * and replaces its inner 'simple' icon with another one recursively
772    * @return original icon with modified inner state
773    */
774   public static Icon replaceInnerIcon(@Nullable Icon icon, @NotNull Icon toCheck, @NotNull Icon toReplace) {
775     if (icon  instanceof LayeredIcon) {
776       Icon[] layers = ((LayeredIcon)icon).getAllLayers();
777       for (int i = 0; i < layers.length; i++) {
778         Icon layer = layers[i];
779         if (layer == toCheck) {
780           layers[i] = toReplace;
781         } else {
782           replaceInnerIcon(layer, toCheck, toReplace);
783         }
784       }
785     }
786     else if (icon instanceof RowIcon) {
787       Icon[] allIcons = ((RowIcon)icon).getAllIcons();
788       for (int i = 0; i < allIcons.length; i++) {
789         Icon anIcon = allIcons[i];
790         if (anIcon == toCheck) {
791           ((RowIcon)icon).setIcon(toReplace, i);
792         }
793         else {
794           replaceInnerIcon(anIcon, toCheck, toReplace);
795         }
796       }
797     }
798     return icon;
799   }
800 }