mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
[pycharm] PY-80467 Debugger: add separate channel view
(cherry picked from commit 6a3b002297a6892e3b5cf9fe54bfa7db3c1cabf8) GitOrigin-RevId: 75b6f24c933df27bbefc1af1d557341bfafa4353
This commit is contained in:
committed by
intellij-monorepo-bot
parent
67c4c7c75f
commit
8372da1b56
@@ -95,4 +95,7 @@ image.color.mode.inverted.image=Inverted
|
||||
image.color.mode.grayscale.image=Grayscale
|
||||
image.color.mode.binarize.image=Binary
|
||||
image.binarize.dialog.title=Set Binarization Threshold
|
||||
image.color.mode.configure.actions=Configure\u2026
|
||||
image.channels.mode.channel.1=Channel 1
|
||||
image.channels.mode.channel.2=Channel 2
|
||||
image.channels.mode.channel.3=Channel 3
|
||||
image.color.mode.configure.actions=Binarization\u2026
|
||||
@@ -0,0 +1,78 @@
|
||||
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 com.intellij.openapi.vfs.writeBytes
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.intellij.images.editor.ImageDocument.IMAGE_DOCUMENT_DATA_KEY
|
||||
import org.intellij.images.scientific.statistics.ScientificImageActionsCollector
|
||||
import org.intellij.images.scientific.utils.ScientificUtils
|
||||
import org.intellij.images.scientific.utils.ScientificUtils.DEFAULT_IMAGE_FORMAT
|
||||
import org.intellij.images.scientific.utils.ScientificUtils.ORIGINAL_IMAGE_KEY
|
||||
import org.intellij.images.scientific.utils.ScientificUtils.ROTATION_ANGLE_KEY
|
||||
import org.intellij.images.scientific.utils.launchBackground
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.ByteArrayOutputStream
|
||||
import javax.imageio.ImageIO
|
||||
|
||||
class DisplaySingleChannelAction(
|
||||
private val channelIndex: Int,
|
||||
text: String
|
||||
) : DumbAwareAction(text) {
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val imageFile = e.getData(CommonDataKeys.VIRTUAL_FILE)
|
||||
val originalImage = imageFile?.getUserData(ORIGINAL_IMAGE_KEY)
|
||||
e.presentation.isEnabled = originalImage != null && getDisplayableChannels(originalImage) > 1
|
||||
}
|
||||
|
||||
|
||||
private fun getDisplayableChannels(image: BufferedImage): Int {
|
||||
val totalChannels = image.raster.numBands
|
||||
return if (image.colorModel.hasAlpha()) totalChannels - 1 else totalChannels
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val imageFile = e.getData(CommonDataKeys.VIRTUAL_FILE) ?: return
|
||||
val originalImage = imageFile.getUserData(ORIGINAL_IMAGE_KEY) ?: return
|
||||
val document = e.getData(IMAGE_DOCUMENT_DATA_KEY) ?: return
|
||||
val currentAngle = imageFile.getUserData(ROTATION_ANGLE_KEY) ?: 0
|
||||
ScientificImageActionsCollector.logChannelSelection(this, channelIndex)
|
||||
|
||||
launchBackground {
|
||||
val rotatedOriginal = if (currentAngle != 0) {
|
||||
ScientificUtils.rotateImage(originalImage, currentAngle)
|
||||
}
|
||||
else {
|
||||
originalImage
|
||||
}
|
||||
val channelImage = displaySingleChannel(rotatedOriginal, channelIndex)
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
ImageIO.write(channelImage, DEFAULT_IMAGE_FORMAT, byteArrayOutputStream)
|
||||
imageFile.writeBytes(byteArrayOutputStream.toByteArray())
|
||||
|
||||
document.value = channelImage
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun displaySingleChannel(image: BufferedImage, channelIndex: Int): BufferedImage = withContext(Dispatchers.IO) {
|
||||
val raster = image.raster
|
||||
val width = image.width
|
||||
val height = image.height
|
||||
|
||||
val channelImage = BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY)
|
||||
|
||||
for (x in 0 until width) {
|
||||
for (y in 0 until height) {
|
||||
val value = raster.getSample(x, y, channelIndex)
|
||||
channelImage.raster.setSample(x, y, 0, value)
|
||||
}
|
||||
}
|
||||
channelImage
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,7 @@ import com.intellij.openapi.actionSystem.*
|
||||
import com.intellij.openapi.actionSystem.ex.CustomComponentAction
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.ui.Gray
|
||||
import com.intellij.ui.JBColor
|
||||
import org.intellij.images.ImagesBundle
|
||||
import org.intellij.images.scientific.utils.ScientificUtils
|
||||
import org.jetbrains.annotations.Nls
|
||||
@@ -28,17 +25,6 @@ class ImageOperationsActionGroup : DefaultActionGroup(), CustomComponentAction,
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val component = e.inputEvent?.source as? JComponent ?: return
|
||||
JBPopupFactory.getInstance().createActionGroupPopup(
|
||||
null,
|
||||
createPopupActionGroup(),
|
||||
e.dataContext,
|
||||
JBPopupFactory.ActionSelectionAid.SPEEDSEARCH,
|
||||
true
|
||||
).showUnderneathOf(component)
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val shouldShowTheGroup = Registry.`is`("ide.images.sci.mode.channels.operations")
|
||||
if (!shouldShowTheGroup) {
|
||||
@@ -57,6 +43,9 @@ class ImageOperationsActionGroup : DefaultActionGroup(), CustomComponentAction,
|
||||
addElement(INVERTED_IMAGE)
|
||||
addElement(GRAYSCALE_IMAGE)
|
||||
addElement(BINARIZE_IMAGE)
|
||||
addElement(CHANNEL_1)
|
||||
addElement(CHANNEL_2)
|
||||
addElement(CHANNEL_3)
|
||||
addElement(CONFIGURE_ACTIONS)
|
||||
}
|
||||
|
||||
@@ -64,21 +53,15 @@ class ImageOperationsActionGroup : DefaultActionGroup(), CustomComponentAction,
|
||||
selectedItem = selectedMode
|
||||
isOpaque = false
|
||||
renderer = object : DefaultListCellRenderer() {
|
||||
override fun getListCellRendererComponent(
|
||||
list: JList<out Any>?,
|
||||
value: Any?,
|
||||
index: Int,
|
||||
isSelected: Boolean,
|
||||
cellHasFocus: Boolean,
|
||||
): JComponent {
|
||||
override fun getListCellRendererComponent(list: JList<out Any>?, value: Any?, index: Int, isSelected: Boolean, cellHasFocus: Boolean): JComponent {
|
||||
val component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus)
|
||||
if (index != -1 && value == CONFIGURE_ACTIONS) {
|
||||
return JSeparator().apply {
|
||||
if (index != -1 && (value == BINARIZE_IMAGE || value == ORIGINAL_IMAGE || value == CHANNEL_3)) {
|
||||
return JPanel().apply {
|
||||
icon = if (value == CONFIGURE_ACTIONS) AllIcons.General.Settings else null
|
||||
layout = BoxLayout(this, BoxLayout.Y_AXIS)
|
||||
isOpaque = false
|
||||
background = JBColor(Gray._200, Gray._100)
|
||||
add(component)
|
||||
add(JSeparator())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,17 +69,29 @@ class ImageOperationsActionGroup : DefaultActionGroup(), CustomComponentAction,
|
||||
}
|
||||
}
|
||||
addActionListener {
|
||||
val selectedItem = selectedItem as String
|
||||
if (selectedItem == CONFIGURE_ACTIONS) {
|
||||
this.selectedItem = selectedMode
|
||||
triggerModeAction(CONFIGURE_ACTIONS)
|
||||
}
|
||||
else {
|
||||
selectedMode = selectedItem
|
||||
triggerModeAction(selectedMode)
|
||||
val selectedItem = selectedItem
|
||||
when (selectedItem) {
|
||||
CONFIGURE_ACTIONS -> {
|
||||
this.selectedItem = selectedMode
|
||||
triggerModeAction(CONFIGURE_ACTIONS)
|
||||
}
|
||||
in listOf(CHANNEL_1, CHANNEL_2, CHANNEL_3) -> {
|
||||
val channelIndex = when (selectedItem) {
|
||||
CHANNEL_1 -> 0
|
||||
CHANNEL_2 -> 1
|
||||
CHANNEL_3 -> 2
|
||||
else -> return@addActionListener
|
||||
}
|
||||
ActionManager.getInstance().tryToExecute(DisplaySingleChannelAction(channelIndex, selectedItem as String), null, null, null, true)
|
||||
}
|
||||
else -> {
|
||||
selectedMode = selectedItem as String
|
||||
triggerModeAction(selectedMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JPanel(FlowLayout(FlowLayout.CENTER, 0, 0)).apply {
|
||||
isOpaque = false
|
||||
border = null
|
||||
@@ -104,16 +99,6 @@ class ImageOperationsActionGroup : DefaultActionGroup(), CustomComponentAction,
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPopupActionGroup(): DefaultActionGroup {
|
||||
val actionGroup = DefaultActionGroup()
|
||||
actionGroup.add(RestoreOriginalImageAction())
|
||||
actionGroup.add(InvertChannelsAction())
|
||||
actionGroup.add(GrayscaleImageAction())
|
||||
actionGroup.add(BinarizeImageAction())
|
||||
actionGroup.add(ConfigureActions())
|
||||
return actionGroup
|
||||
}
|
||||
|
||||
private fun triggerModeAction(mode: String) {
|
||||
val actionManager = ActionManager.getInstance()
|
||||
when (mode) {
|
||||
@@ -126,18 +111,20 @@ class ImageOperationsActionGroup : DefaultActionGroup(), CustomComponentAction,
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Nls
|
||||
private val CHANNEL_1: String = ImagesBundle.message("image.channels.mode.channel.1")
|
||||
@Nls
|
||||
private val CHANNEL_2: String = ImagesBundle.message("image.channels.mode.channel.2")
|
||||
@Nls
|
||||
private val CHANNEL_3: String = ImagesBundle.message("image.channels.mode.channel.3")
|
||||
@Nls
|
||||
private val ORIGINAL_IMAGE: String = ImagesBundle.message("image.color.mode.original.image")
|
||||
|
||||
@Nls
|
||||
private val INVERTED_IMAGE: String = ImagesBundle.message("image.color.mode.inverted.image")
|
||||
|
||||
@Nls
|
||||
private val GRAYSCALE_IMAGE: String = ImagesBundle.message("image.color.mode.grayscale.image")
|
||||
|
||||
@Nls
|
||||
private val BINARIZE_IMAGE: String = ImagesBundle.message("image.color.mode.binarize.image")
|
||||
|
||||
@Nls
|
||||
private val CONFIGURE_ACTIONS: String = ImagesBundle.message("image.color.mode.configure.actions")
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@ import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesColle
|
||||
import org.intellij.images.scientific.action.*
|
||||
|
||||
object ScientificImageActionsCollector : CounterUsagesCollector() {
|
||||
private val GROUP = EventLogGroup("scientific.image.actions", 2)
|
||||
private val GROUP = EventLogGroup("scientific.image.actions", 3)
|
||||
|
||||
private val ACTION_HANDLER_FIELD = EventFields.Class("action_handler")
|
||||
private val IMAGE_FORMAT_FIELD = EventFields.String("image_format", listOf("png", "jpg", "jpeg", "bmp"))
|
||||
private val IMAGE_FORMAT_FIELD = EventFields.String("image_format", listOf("png", "jpg", "jpeg", "bmp", "svg"))
|
||||
private val CHANNEL_INDEX_FIELD = EventFields.Int("channel_index")
|
||||
|
||||
private val INVOKED_COPY_IMAGE_EVENT = GROUP.registerEvent("copy_image_action", ACTION_HANDLER_FIELD)
|
||||
private val INVOKED_SAVE_IMAGE_EVENT = GROUP.registerEvent("save_image_action", ACTION_HANDLER_FIELD, IMAGE_FORMAT_FIELD)
|
||||
@@ -18,6 +19,7 @@ object ScientificImageActionsCollector : CounterUsagesCollector() {
|
||||
private val INVOKED_GRAYSCALE_IMAGE_EVENT = GROUP.registerEvent("grayscale_image_action", ACTION_HANDLER_FIELD)
|
||||
private val INVOKED_BINARY_IMAGE_EVENT = GROUP.registerEvent("binarize_image_action", ACTION_HANDLER_FIELD)
|
||||
private val INVOKED_ROTATE_IMAGE_EVENT = GROUP.registerEvent("rotate_image_action", ACTION_HANDLER_FIELD)
|
||||
private val CHANNEL_SELECTION_EVENT = GROUP.registerEvent("channel_selection", ACTION_HANDLER_FIELD, CHANNEL_INDEX_FIELD)
|
||||
|
||||
override fun getGroup(): EventLogGroup = GROUP
|
||||
|
||||
@@ -48,4 +50,8 @@ object ScientificImageActionsCollector : CounterUsagesCollector() {
|
||||
fun logRotateImageInvoked(action: RotateImageAction) {
|
||||
INVOKED_ROTATE_IMAGE_EVENT.log(action::class.java)
|
||||
}
|
||||
|
||||
fun logChannelSelection(action: DisplaySingleChannelAction, channelIndex: Int) {
|
||||
CHANNEL_SELECTION_EVENT.log(action::class.java, channelIndex)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user