diff --git a/images/resources/messages/ImagesBundle.properties b/images/resources/messages/ImagesBundle.properties index 3d8fe4b4b858..9b89e526ef50 100644 --- a/images/resources/messages/ImagesBundle.properties +++ b/images/resources/messages/ImagesBundle.properties @@ -89,7 +89,6 @@ image.description={0}\u00D7{1}, {2}bpp dialog.title.save.image=Select File to Save Image action.Images.SaveImageAction.text=Save Image action.Images.CopyImageAction.text=Copy Image -group.Images.ImageOperationsGroup.text=Hi there -image.color.mode.rgb=RGB -image.color.mode.bgr=BGR -image.color.mode.grayscale=Grayscale \ No newline at end of file +image.color.mode.original.image=Original +image.color.mode.inverted.image=Inverted +image.color.mode.grayscale.image=Grayscale \ No newline at end of file diff --git a/images/src/org/intellij/images/scientific/action/BGRAction.kt b/images/src/org/intellij/images/scientific/action/BGRAction.kt new file mode 100644 index 000000000000..feb1967a9f2d --- /dev/null +++ b/images/src/org/intellij/images/scientific/action/BGRAction.kt @@ -0,0 +1,53 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.intellij.images.scientific.action + +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.project.DumbAwareAction +import org.intellij.images.editor.ImageEditor +import org.intellij.images.scientific.ScientificUtils +import org.intellij.images.ui.ImageComponent +import java.awt.image.BufferedImage + +class BGRAction : DumbAwareAction() { + + override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT + + override fun update(e: AnActionEvent) { + val imageFile = e.getData(CommonDataKeys.VIRTUAL_FILE) + e.presentation.isEnabledAndVisible = imageFile?.getUserData(ScientificUtils.SCIENTIFIC_MODE_KEY) != null + } + + override fun actionPerformed(e: AnActionEvent) { + val imageEditor = getImageEditor(e) ?: return + val imageComponent = getImageComponent(imageEditor) ?: return + val document = imageComponent.document + val originalImage = document.value ?: return + val bgrImage = applyInvertChannels(originalImage) + document.setValue(bgrImage) // TODO: how to view result without changing original image? + } + + private fun applyInvertChannels(image: BufferedImage): BufferedImage { + val invertedImage = BufferedImage(image.width, image.height, image.type) + for (x in 0 until image.width) { + for (y in 0 until image.height) { + val rgb = image.getRGB(x, y) + val red = (rgb shr 16) and 0xFF + val green = (rgb shr 8) and 0xFF + val blue = rgb and 0xFF + val bgr = (blue shl 16) or (green shl 8) or red + invertedImage.setRGB(x, y, bgr) + } + } + return invertedImage + } + + private fun getImageEditor(e: AnActionEvent): ImageEditor? { + return null + } + + private fun getImageComponent(imageEditor: ImageEditor): ImageComponent? { + return null + } +} \ No newline at end of file diff --git a/images/src/org/intellij/images/scientific/action/GrayscaleAction.kt b/images/src/org/intellij/images/scientific/action/GrayscaleAction.kt new file mode 100644 index 000000000000..a2c974f34175 --- /dev/null +++ b/images/src/org/intellij/images/scientific/action/GrayscaleAction.kt @@ -0,0 +1,54 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.intellij.images.scientific.action + +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.project.DumbAwareAction +import org.intellij.images.editor.ImageEditor +import org.intellij.images.scientific.ScientificUtils +import org.intellij.images.ui.ImageComponent +import java.awt.Color +import java.awt.image.BufferedImage + +class GrayscaleAction : DumbAwareAction() { + + override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT + + override fun update(e: AnActionEvent) { + val imageFile = e.getData(CommonDataKeys.VIRTUAL_FILE) + e.presentation.isEnabledAndVisible = imageFile?.getUserData(ScientificUtils.SCIENTIFIC_MODE_KEY) != null + } + + override fun actionPerformed(e: AnActionEvent) { + + val imageEditor = getImageEditor(e) ?: return + val imageComponent = getImageComponent(imageEditor) ?: return + val document = imageComponent.document + val originalImage = document.value ?: return + val grayscaleImage = applyGrayscale(originalImage) + document.setValue(grayscaleImage) // TODO: how to view result without changing original image? + } + + private fun getImageEditor(e: AnActionEvent): ImageEditor? { + return null + } + + private fun getImageComponent(imageEditor: ImageEditor): ImageComponent? { + return null + } + + // TODO: maybe redo logic, its just a stub! + private fun applyGrayscale(image: BufferedImage): BufferedImage { + val grayscaleImage = BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB) + for (x in 0 until image.width) { + for (y in 0 until image.height) { + val pixel = Color(image.getRGB(x, y)) + val gray = (pixel.red * 0.299 + pixel.green * 0.587 + pixel.blue * 0.114).toInt() + val grayColor = Color(gray, gray, gray) + grayscaleImage.setRGB(x, y, grayColor.rgb) + } + } + return grayscaleImage + } +} \ No newline at end of file diff --git a/images/src/org/intellij/images/scientific/action/ImageOperationsActionGroup.kt b/images/src/org/intellij/images/scientific/action/ImageOperationsActionGroup.kt index 762d6f083a1a..0db1679cafd3 100644 --- a/images/src/org/intellij/images/scientific/action/ImageOperationsActionGroup.kt +++ b/images/src/org/intellij/images/scientific/action/ImageOperationsActionGroup.kt @@ -3,7 +3,6 @@ package org.intellij.images.scientific.action import com.intellij.openapi.actionSystem.* import com.intellij.openapi.actionSystem.ex.CustomComponentAction import com.intellij.openapi.project.DumbAware -import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.popup.JBPopupFactory import org.intellij.images.ImagesBundle @@ -15,8 +14,8 @@ import javax.swing.JPanel class ImageOperationsActionGroup : DefaultActionGroup(), CustomComponentAction, DumbAware { - private var selectedMode: String = RGB - private val availableModes = listOf(RGB, BGR, GRAYSCALE) + private var selectedMode: String = ORIGINAL_IMAGE + private val availableModes = listOf(ORIGINAL_IMAGE, INVERTED_IMAGE, GRAYSCALE_IMAGE) init { templatePresentation.apply { @@ -33,9 +32,8 @@ class ImageOperationsActionGroup : DefaultActionGroup(), CustomComponentAction, null, createPopupActionGroup(), e.dataContext, - null, - true, - null + JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, + true ).showUnderneathOf(component) } @@ -50,6 +48,7 @@ class ImageOperationsActionGroup : DefaultActionGroup(), CustomComponentAction, isOpaque = false addActionListener { selectedMode = selectedItem as String + triggerModeAction(selectedMode) } } return JPanel(BorderLayout()).apply { @@ -61,19 +60,24 @@ class ImageOperationsActionGroup : DefaultActionGroup(), CustomComponentAction, private fun createPopupActionGroup(): DefaultActionGroup { val actionGroup = DefaultActionGroup() - availableModes.forEach { mode -> - actionGroup.add( - DumbAwareAction.create(mode) { - selectedMode = mode - } - ) - } + actionGroup.add(RGBAction()) + actionGroup.add(BGRAction()) + actionGroup.add(GrayscaleAction()) return actionGroup } + private fun triggerModeAction(mode: String) { + val actionManager = ActionManager.getInstance() + when (mode) { + ORIGINAL_IMAGE -> actionManager.tryToExecute(RGBAction(), null, null, null, true) + INVERTED_IMAGE -> actionManager.tryToExecute(BGRAction(), null, null, null, true) + GRAYSCALE_IMAGE -> actionManager.tryToExecute(GrayscaleAction(), null, null, null, true) + } + } + companion object { - private val RGB: String = ImagesBundle.message("image.color.mode.rgb") - private val BGR: String = ImagesBundle.message("image.color.mode.bgr") - private val GRAYSCALE: String = ImagesBundle.message("image.color.mode.grayscale") + private val ORIGINAL_IMAGE: String = ImagesBundle.message("image.color.mode.original.image") + private val INVERTED_IMAGE: String = ImagesBundle.message("image.color.mode.inverted.image") + private val GRAYSCALE_IMAGE: String = ImagesBundle.message("image.color.mode.grayscale.image") } } \ No newline at end of file diff --git a/images/src/org/intellij/images/scientific/action/RGBAction.kt b/images/src/org/intellij/images/scientific/action/RGBAction.kt new file mode 100644 index 000000000000..cbc734d0fa96 --- /dev/null +++ b/images/src/org/intellij/images/scientific/action/RGBAction.kt @@ -0,0 +1,36 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.intellij.images.scientific.action + +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.project.DumbAwareAction +import org.intellij.images.editor.ImageEditor +import org.intellij.images.scientific.ScientificUtils +import org.intellij.images.ui.ImageComponent + +class RGBAction : DumbAwareAction() { + + override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT + + override fun update(e: AnActionEvent) { + val imageFile = e.getData(CommonDataKeys.VIRTUAL_FILE) + e.presentation.isEnabledAndVisible = imageFile?.getUserData(ScientificUtils.SCIENTIFIC_MODE_KEY) != null + } + + override fun actionPerformed(e: AnActionEvent) { + val imageEditor = getImageEditor(e) ?: return + val imageComponent = getImageComponent(imageEditor) ?: return + val document = imageComponent.document + val originalImage = document.value ?: return + document.setValue(originalImage) // TODO: restore original image! + } + + private fun getImageEditor(e: AnActionEvent): ImageEditor? { + return null + } + + private fun getImageComponent(imageEditor: ImageEditor): ImageComponent? { + return null + } +} \ No newline at end of file