[pycharm] PY-79113 PY-79603 PY-79243 Debugger: add coroutines + tiny refactoring

(cherry picked from commit 36fa13fea7007e57c19d2b267431eb685fd384cc)

GitOrigin-RevId: ff4385a857b8e279b99c8a034b6fb7a67488394e
This commit is contained in:
ekaterina.itsenko
2025-04-11 14:19:12 +02:00
committed by intellij-monorepo-bot
parent b15df32e19
commit d889654caf
13 changed files with 123 additions and 54 deletions

View File

@@ -47,5 +47,6 @@
<orderEntry type="module" module-name="intellij.platform.ui.jcef" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.platform.statistics" />
<orderEntry type="module" module-name="intellij.platform.util.coroutines" />
</component>
</module>

View File

@@ -24,7 +24,7 @@
serviceImplementation="org.intellij.images.fileTypes.impl.ImageFileTypeManagerImpl"/>
<applicationService serviceInterface="org.intellij.images.options.OptionsManager"
serviceImplementation="org.intellij.images.options.impl.OptionsManagerImpl"/>
<applicationService serviceImplementation="org.intellij.images.scientific.BinarizationThresholdConfig"/>
<applicationService serviceImplementation="org.intellij.images.scientific.utils.BinarizationThresholdConfig"/>
<projectService serviceInterface="org.intellij.images.thumbnail.ThumbnailManager"
serviceImplementation="org.intellij.images.thumbnail.impl.ThumbnailManagerImpl"/>
<!-- ImageIO.getReaderFormatNames() -->

View File

@@ -94,5 +94,4 @@ 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.binarize.dialog.invalid=Please enter a valid integer between 0 and 255.
image.color.mode.configure.actions=Configure...
image.color.mode.configure.actions=Configure\u2026

View File

@@ -5,15 +5,19 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.writeBytes
import org.intellij.images.editor.ImageDocument
import org.intellij.images.scientific.BinarizationThresholdConfig
import org.intellij.images.scientific.ScientificUtils
import org.intellij.images.scientific.utils.BinarizationThresholdConfig
import org.intellij.images.scientific.utils.ScientificUtils
import org.intellij.images.scientific.statistics.ScientificImageActionsCollector
import org.intellij.images.scientific.utils.launchBackground
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import javax.imageio.ImageIO
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.intellij.images.scientific.utils.ScientificUtils.DEFAULT_IMAGE_FORMAT
class BinarizeImageAction : AnAction() {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
@@ -21,17 +25,18 @@ class BinarizeImageAction : AnAction() {
override fun actionPerformed(e: AnActionEvent) {
val imageFile = e.getData(CommonDataKeys.VIRTUAL_FILE) ?: return
val originalImage = imageFile.getUserData(ScientificUtils.ORIGINAL_IMAGE_KEY) ?: return
val thresholdConfig = BinarizationThresholdConfig.getInstance()
val byteArrayOutputStream = ByteArrayOutputStream()
val binarizedImage = applyBinarization(originalImage, thresholdConfig.threshold)
ImageIO.write(binarizedImage, ScientificUtils.DEFAULT_IMAGE_FORMAT, byteArrayOutputStream)
imageFile.writeBytes(byteArrayOutputStream.toByteArray())
val document = e.getData(ImageDocument.IMAGE_DOCUMENT_DATA_KEY) ?: return
document.value = binarizedImage
ScientificImageActionsCollector.logBinarizeImageInvoked(this)
val thresholdConfig = BinarizationThresholdConfig.getInstance()
launchBackground {
val binarizedImage = applyBinarization(originalImage, thresholdConfig.threshold)
saveImageToFile(imageFile, binarizedImage)
document.value = binarizedImage
ScientificImageActionsCollector.logBinarizeImageInvoked(this@BinarizeImageAction)
}
}
private fun applyBinarization(image: BufferedImage, threshold: Int): BufferedImage {
private suspend fun applyBinarization(image: BufferedImage, threshold: Int): BufferedImage = withContext(Dispatchers.IO) {
val binarizedImage = BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_ARGB)
for (y in 0 until image.height) {
for (x in 0 until image.width) {
@@ -51,6 +56,12 @@ class BinarizeImageAction : AnAction() {
binarizedImage.setRGB(x, y, finalColor)
}
}
return binarizedImage
binarizedImage
}
private suspend fun saveImageToFile(imageFile: VirtualFile, binarizedImage: BufferedImage) = withContext(Dispatchers.IO) {
val byteArrayOutputStream = ByteArrayOutputStream()
ImageIO.write(binarizedImage, DEFAULT_IMAGE_FORMAT, byteArrayOutputStream)
imageFile.writeBytes(byteArrayOutputStream.toByteArray())
}
}

View File

@@ -7,7 +7,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogWrapper
import org.intellij.images.ImagesBundle
import org.intellij.images.scientific.BinarizationThresholdConfig
import org.intellij.images.scientific.utils.BinarizationThresholdConfig
import java.awt.BorderLayout
import java.awt.Dimension
import javax.swing.JComponent

View File

@@ -6,7 +6,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.project.DumbAwareAction
import org.intellij.images.scientific.ScientificUtils
import org.intellij.images.scientific.utils.ScientificUtils
import org.intellij.images.scientific.statistics.ScientificImageActionsCollector
class CopyImageAction : DumbAwareAction() {

View File

@@ -5,14 +5,18 @@ 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.VirtualFile
import com.intellij.openapi.vfs.writeBytes
import org.intellij.images.editor.ImageDocument.IMAGE_DOCUMENT_DATA_KEY
import org.intellij.images.scientific.ScientificUtils.DEFAULT_IMAGE_FORMAT
import org.intellij.images.scientific.ScientificUtils.ORIGINAL_IMAGE_KEY
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.statistics.ScientificImageActionsCollector
import org.intellij.images.scientific.utils.launchBackground
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import javax.imageio.ImageIO
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class GrayscaleImageAction : DumbAwareAction() {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
@@ -20,20 +24,27 @@ class GrayscaleImageAction : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) {
val imageFile = e.getData(CommonDataKeys.VIRTUAL_FILE) ?: return
val originalImage = imageFile.getUserData(ORIGINAL_IMAGE_KEY) ?: return
val grayscaleImage = applyGrayscale(originalImage)
val byteArrayOutputStream = ByteArrayOutputStream()
ImageIO.write(grayscaleImage, DEFAULT_IMAGE_FORMAT, byteArrayOutputStream)
imageFile.writeBytes(byteArrayOutputStream.toByteArray())
val document = e.getData(IMAGE_DOCUMENT_DATA_KEY) ?: return
document.value = grayscaleImage
ScientificImageActionsCollector.logGrayscaleImageInvoked(this)
launchBackground {
val grayscaleImage = applyGrayscale(originalImage)
saveImageToFile(imageFile, grayscaleImage)
document.value = grayscaleImage
ScientificImageActionsCollector.logGrayscaleImageInvoked(this@GrayscaleImageAction)
}
}
private fun applyGrayscale(image: BufferedImage): BufferedImage {
private suspend fun applyGrayscale(image: BufferedImage): BufferedImage = withContext(Dispatchers.IO) {
val grayscaleImage = BufferedImage(image.width, image.height, BufferedImage.TYPE_BYTE_GRAY)
val graphics = grayscaleImage.createGraphics()
graphics.drawImage(image, 0, 0, null)
graphics.dispose()
return grayscaleImage
grayscaleImage
}
private suspend fun saveImageToFile(imageFile: VirtualFile, grayscaleImage: BufferedImage) = withContext(Dispatchers.IO) {
val byteArrayOutputStream = ByteArrayOutputStream()
ImageIO.write(grayscaleImage, DEFAULT_IMAGE_FORMAT, byteArrayOutputStream)
imageFile.writeBytes(byteArrayOutputStream.toByteArray())
}
}

View File

@@ -11,7 +11,7 @@ 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.ScientificUtils
import org.intellij.images.scientific.utils.ScientificUtils
import org.jetbrains.annotations.Nls
import java.awt.FlowLayout
import javax.swing.*

View File

@@ -5,14 +5,18 @@ 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.VirtualFile
import com.intellij.openapi.vfs.writeBytes
import org.intellij.images.editor.ImageDocument.IMAGE_DOCUMENT_DATA_KEY
import org.intellij.images.scientific.ScientificUtils.DEFAULT_IMAGE_FORMAT
import org.intellij.images.scientific.ScientificUtils.ORIGINAL_IMAGE_KEY
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.statistics.ScientificImageActionsCollector
import org.intellij.images.scientific.utils.launchBackground
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import javax.imageio.ImageIO
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class InvertChannelsAction : DumbAwareAction() {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
@@ -20,17 +24,17 @@ class InvertChannelsAction : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) {
val imageFile = e.getData(CommonDataKeys.VIRTUAL_FILE) ?: return
val originalImage = imageFile.getUserData(ORIGINAL_IMAGE_KEY) ?: return
val invertedImage = applyInvertChannels(originalImage)
val byteArrayOutputStream = ByteArrayOutputStream()
ImageIO.write(invertedImage, DEFAULT_IMAGE_FORMAT, byteArrayOutputStream)
imageFile.writeBytes(byteArrayOutputStream.toByteArray())
val document = e.getData(IMAGE_DOCUMENT_DATA_KEY) ?: return
document.value = invertedImage
ScientificImageActionsCollector.logInvertChannelsInvoked(this)
launchBackground {
val invertedImage = applyInvertChannels(originalImage)
saveImageToFile(imageFile, invertedImage)
document.value = invertedImage
ScientificImageActionsCollector.logInvertChannelsInvoked(this@InvertChannelsAction)
}
}
private fun applyInvertChannels(image: BufferedImage): BufferedImage {
private suspend fun applyInvertChannels(image: BufferedImage): BufferedImage = withContext(Dispatchers.IO) {
val hasAlpha = image.colorModel.hasAlpha()
val invertedImage = BufferedImage(image.width, image.height, if (hasAlpha) BufferedImage.TYPE_INT_ARGB else BufferedImage.TYPE_INT_RGB)
for (x in 0 until image.width) {
@@ -47,6 +51,12 @@ class InvertChannelsAction : DumbAwareAction() {
invertedImage.setRGB(x, y, invertedRgba)
}
}
return invertedImage
invertedImage
}
private suspend fun saveImageToFile(imageFile: VirtualFile, invertedImage: BufferedImage) = withContext(Dispatchers.IO) {
val byteArrayOutputStream = ByteArrayOutputStream()
ImageIO.write(invertedImage, DEFAULT_IMAGE_FORMAT, byteArrayOutputStream)
imageFile.writeBytes(byteArrayOutputStream.toByteArray())
}
}

View File

@@ -5,13 +5,18 @@ 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.VirtualFile
import com.intellij.openapi.vfs.writeBytes
import org.intellij.images.editor.ImageDocument.IMAGE_DOCUMENT_DATA_KEY
import org.intellij.images.scientific.ScientificUtils.DEFAULT_IMAGE_FORMAT
import org.intellij.images.scientific.ScientificUtils.ORIGINAL_IMAGE_KEY
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.statistics.ScientificImageActionsCollector
import org.intellij.images.scientific.utils.launchBackground
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import javax.imageio.ImageIO
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class RestoreOriginalImageAction : DumbAwareAction() {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
@@ -19,11 +24,18 @@ class RestoreOriginalImageAction : DumbAwareAction() {
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
launchBackground {
saveImageToFile(imageFile, originalImage)
document.value = originalImage
ScientificImageActionsCollector.logRestoreOriginalImageInvoked(this@RestoreOriginalImageAction)
}
}
private suspend fun saveImageToFile(imageFile: VirtualFile, originalImage: BufferedImage) = withContext(Dispatchers.IO) {
val byteArrayOutputStream = ByteArrayOutputStream()
ImageIO.write(originalImage, DEFAULT_IMAGE_FORMAT, byteArrayOutputStream)
imageFile.writeBytes(byteArrayOutputStream.toByteArray())
val document = e.getData(IMAGE_DOCUMENT_DATA_KEY) ?: return
document.value = originalImage
ScientificImageActionsCollector.logRestoreOriginalImageInvoked(this)
}
}

View File

@@ -10,12 +10,16 @@ import com.intellij.openapi.fileChooser.FileSaverDescriptor
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.vfs.VirtualFileManager
import org.intellij.images.ImagesBundle
import org.intellij.images.scientific.ScientificUtils
import org.intellij.images.scientific.utils.ScientificUtils
import org.intellij.images.scientific.statistics.ScientificImageActionsCollector
import org.intellij.images.scientific.utils.launchBackground
import java.io.IOException
import java.nio.file.Files
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
internal class SaveImageAction : DumbAwareAction() {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
override fun actionPerformed(e: AnActionEvent) {
val project = e.project
@@ -33,13 +37,17 @@ internal class SaveImageAction : DumbAwareAction() {
val targetFile = wrapper.file
val selectedFormat = targetFile.extension.lowercase()
try {
Files.write(wrapper.file.toPath(), virtualFile.contentsToByteArray())
launchBackground {
withContext(Dispatchers.IO) {
try {
Files.write(wrapper.file.toPath(), virtualFile.contentsToByteArray())
}
catch (e: IOException) {
logger.warn("Failed to save image", e)
}
ScientificImageActionsCollector.logSaveAsImageInvoked(this@SaveImageAction, selectedFormat)
}
}
catch (e: IOException) {
logger.warn("Failed to save image", e)
}
ScientificImageActionsCollector.logSaveAsImageInvoked(this, selectedFormat)
}
override fun update(e: AnActionEvent) {
@@ -47,8 +55,6 @@ internal class SaveImageAction : DumbAwareAction() {
e.presentation.isEnabledAndVisible = imageFile?.getUserData(ScientificUtils.SCIENTIFIC_MODE_KEY) != null
}
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
companion object {
private const val IMAGE_FORMAT = "png"
private const val IMAGE_DEFAULT_NAME: String = "myimg"

View File

@@ -0,0 +1,19 @@
package org.intellij.images.scientific.utils
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
@Service
class ScientificImageViewerCoroutine(val coroutineScope: CoroutineScope) {
object Utils {
val scope: CoroutineScope
get() = ApplicationManager.getApplication().service<ScientificImageViewerCoroutine>().coroutineScope
}
}
fun launchBackground(block: suspend CoroutineScope.() -> Unit): Job = ScientificImageViewerCoroutine.Utils.scope.launch(block = block)

View File

@@ -1,5 +1,5 @@
// 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
package org.intellij.images.scientific.utils
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.Service