Skip to content

Commit

Permalink
Merge pull request #236 from valb3r/feature/FBP-231-save-diagram-as-png
Browse files Browse the repository at this point in the history
Feature/fbp 231 save diagram as png
  • Loading branch information
valb3r authored Jul 23, 2021
2 parents 939a3fd + 5fd4cab commit 03e8fc9
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.valb3r.bpmn.intellij.plugin.core.actions.copypaste.copyPasteActionHan
import com.valb3r.bpmn.intellij.plugin.core.actions.copypaste.copyToClipboard
import com.valb3r.bpmn.intellij.plugin.core.actions.copypaste.cutToClipboard
import com.valb3r.bpmn.intellij.plugin.core.actions.copypaste.pasteFromClipboard
import com.valb3r.bpmn.intellij.plugin.core.actions.saveDiagramToPng
import com.valb3r.bpmn.intellij.plugin.core.render.lastRenderedState
import com.valb3r.bpmn.intellij.plugin.core.ui.components.popupmenu.CanvasPopupMenuProvider
import java.awt.event.ActionListener
Expand All @@ -43,6 +44,7 @@ class ActivitiCanvasPopupMenuProvider(private val project: Project) : CanvasPopu
private val COPY = IconLoader.getIcon("/icons/actions/copy.png")
private val CUT = IconLoader.getIcon("/icons/actions/cut.png")
private val PASTE = IconLoader.getIcon("/icons/actions/paste.png")
private val SAVE_TO_PNG = IconLoader.getIcon("/icons/actions/save-to-png.png")

// Events
// Start
Expand Down Expand Up @@ -107,6 +109,7 @@ class ActivitiCanvasPopupMenuProvider(private val project: Project) : CanvasPopu
popup.add(intermediateCatchingEvents(sceneLocation, parent))
popup.add(intermediateThrowingEvents(sceneLocation, parent))
popup.add(endEvents(sceneLocation, parent))
addItem(popup, "Save to PNG", SAVE_TO_PNG, ActionListener { saveDiagramToPng(project) })
return popup
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.intellij.ui.JBColor
import java.awt.Color

enum class Colors(val color: JBColor) {
TRANSPARENT(JBColor(Color(0x000000FF, true), Color(0x000000FF, true))),
SERVICE_TASK_COLOR(JBColor(Color(0xF9F9F9), Color(0x535353))),
TRANSACTION_COLOR(JBColor(Color(0xFDFEFF), Color(0x292B2D))),
TRANSACTION_ELEMENT_BORDER_COLOR(JBColor(Color(0x292B2D), Color(0xC9C9C9))),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.valb3r.bpmn.intellij.plugin.core.actions

import com.intellij.openapi.fileChooser.FileChooserFactory
import com.intellij.openapi.fileChooser.FileSaverDescriptor
import com.intellij.openapi.project.Project
import com.valb3r.bpmn.intellij.plugin.core.render.currentCanvas
import javax.imageio.ImageIO

fun saveDiagramToPng(project: Project) {
val canvas = currentCanvas(project)
val image = canvas.renderToBitmap() ?: return
val descriptor = FileSaverDescriptor("Save Diagram To", "Save diagram to file", "png")
val dialog = FileChooserFactory.getInstance().createSaveFileDialog(descriptor, project)
val dest = dialog.save(null, "diagram") ?: return
ImageIO.write(image, "png", dest.file)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.valb3r.bpmn.intellij.plugin.core.render
import com.google.common.annotations.VisibleForTesting
import com.google.common.cache.CacheBuilder
import com.intellij.openapi.project.Project
import com.intellij.ui.JBColor
import com.intellij.util.ui.UIUtil
import com.valb3r.bpmn.intellij.plugin.bpmn.api.BpmnProcessObjectView
import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.BpmnElementId
import com.valb3r.bpmn.intellij.plugin.bpmn.api.bpmn.elements.BpmnSequenceFlow
Expand All @@ -15,6 +17,7 @@ import com.valb3r.bpmn.intellij.plugin.core.render.elements.edges.BaseEdgeRender
import com.valb3r.bpmn.intellij.plugin.core.render.uieventbus.*
import com.valb3r.bpmn.intellij.plugin.core.settings.currentSettings
import com.valb3r.bpmn.intellij.plugin.core.state.currentStateProvider
import java.awt.Color
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.RenderingHints
Expand Down Expand Up @@ -120,6 +123,49 @@ class Canvas(private val project: Project, private val settings: CanvasConstants
)
}

fun renderToBitmap() : BufferedImage? {
val doRender = { image: BufferedImage, ctx: ElementInteractionContext, camera: Camera ->
val graphics = setupGraphicsAntialiasing(image.createGraphics())
// fill the background for entire canvas
graphics.color = Colors.TRANSPARENT.color
graphics.fillRect(0, 0, image.width, image.height)
renderer?.renderOnlyDiagram(
RenderContext(
project,
CanvasPainter(graphics, camera, cachedIcons),
setOf(),
ctx,
stateProvider
)
)
}
val interactionContext = ElementInteractionContext(emptySet(), emptySet(), mutableMapOf(), null, mutableMapOf(), null, Point2D.Float(), Point2D.Float())
val dummyImage = UIUtil.createImage(1, 1, BufferedImage.TYPE_INT_RGB)
val dimensions = doRender(dummyImage, interactionContext, Camera(Point2D.Float(0.0f, 0.0f), Point2D.Float(1.0f, 1.0f))) ?: return null
val maxX = dimensions.map { it.value.area.bounds2D.maxX }.max()?.toInt() ?: return null
val minX = dimensions.map { it.value.area.bounds2D.minX }.min()?.toInt() ?: return null
val maxY = dimensions.map { it.value.area.bounds2D.maxY }.max()?.toInt() ?: return null
val minY = dimensions.map { it.value.area.bounds2D.minY }.min()?.toInt() ?: return null
val width = maxX - minX
val height = maxY - minY

val borderSpaceCoeff = 1.05f
val renderedImage = UIUtil.createImage(
(width * borderSpaceCoeff).toInt(),
(height * borderSpaceCoeff).toInt(),
BufferedImage.TYPE_INT_ARGB
)
doRender(
renderedImage,
interactionContext,
Camera(
Point2D.Float(width * (1.0f - borderSpaceCoeff) / 2.0f, height * (1.0f - borderSpaceCoeff) / 2.0f),
Point2D.Float(1.0f, 1.0f)
)
)
return renderedImage
}

fun reset(fileContent: String, processObject: BpmnProcessObjectView, renderer: BpmnProcessRenderer) {
this.renderer = renderer
this.latestOnScreenModelDimensions = null
Expand Down Expand Up @@ -475,11 +521,16 @@ class Canvas(private val project: Project, private val settings: CanvasConstants
}


private fun setupGraphics(graphics: Graphics): Graphics2D {
private fun setupGraphicsAntialiasing(graphics: Graphics): Graphics2D {
// set up the drawing panel
val graphics2D = graphics as Graphics2D
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
graphics2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON)
return graphics2D
}

private fun setupGraphics(graphics: Graphics): Graphics2D {
val graphics2D = setupGraphicsAntialiasing(graphics)

// fill the background for entire canvas
graphics2D.color = Colors.BACKGROUND_COLOR.color
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import javax.swing.Icon

interface BpmnProcessRenderer {
fun render(ctx: RenderContext): Map<DiagramElementId, AreaWithZindex>
fun renderOnlyDiagram(ctx: RenderContext): Map<DiagramElementId, AreaWithZindex>
}

private val lastState = Collections.synchronizedMap(WeakHashMap<Project, RenderedState>())
Expand Down Expand Up @@ -98,17 +99,25 @@ class DefaultBpmnProcessRenderer(private val project: Project, val icons: IconPr
private val ACTION_AREA_STROKE = BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, floatArrayOf(2.0f), 0.0f)

override fun render(ctx: RenderContext): Map<DiagramElementId, AreaWithZindex> {
return doRender(ctx)
}

override fun renderOnlyDiagram(ctx: RenderContext): Map<DiagramElementId, AreaWithZindex> {
return doRender(ctx, true)
}

private fun doRender(ctx: RenderContext, onlyDiagram: Boolean = false): MutableMap<DiagramElementId, AreaWithZindex> {
val elementsByDiagramId = mutableMapOf<DiagramElementId, BaseDiagramRenderElement>()
val currentState = ctx.stateProvider.currentState()
val history = currentDebugger(project)?.executionSequence(project, currentState.processId.id)?.history ?: emptyList()
val state = RenderState(
elementsByDiagramId,
currentState,
history,
ctx,
icons
elementsByDiagramId,
currentState,
history,
ctx,
icons
)

val elements = mutableListOf<BaseBpmnRenderElement>()
val elementsById = mutableMapOf<BpmnElementId, BaseDiagramRenderElement>()
val root = createRootProcessElem(state, elements, elementsById)
Expand All @@ -122,6 +131,10 @@ class DefaultBpmnProcessRenderer(private val project: Project, val icons: IconPr
val rendered = root.render()
val modelRect = computeModelRect(rendered.values)

if (onlyDiagram) {
return rendered
}

// Overlay system elements on top of rendered BPMN diagram
ctx.interactionContext.anchorsHit?.apply { drawAnchorsHit(ctx.canvas, this) }
drawUndoRedoAndZoomIcons(ctx.project, state, rendered)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.valb3r.bpmn.intellij.plugin.core.actions.copypaste.copyPasteActionHan
import com.valb3r.bpmn.intellij.plugin.core.actions.copypaste.copyToClipboard
import com.valb3r.bpmn.intellij.plugin.core.actions.copypaste.cutToClipboard
import com.valb3r.bpmn.intellij.plugin.core.actions.copypaste.pasteFromClipboard
import com.valb3r.bpmn.intellij.plugin.core.actions.saveDiagramToPng
import com.valb3r.bpmn.intellij.plugin.core.render.lastRenderedState
import com.valb3r.bpmn.intellij.plugin.core.ui.components.popupmenu.CanvasPopupMenuProvider
import java.awt.event.ActionListener
Expand All @@ -43,6 +44,7 @@ class FlowableCanvasPopupMenuProvider(private val project: Project) : CanvasPopu
private val COPY = IconLoader.getIcon("/icons/actions/copy.png")
private val CUT = IconLoader.getIcon("/icons/actions/cut.png")
private val PASTE = IconLoader.getIcon("/icons/actions/paste.png")
private val SAVE_TO_PNG = IconLoader.getIcon("/icons/actions/save-to-png.png")

// Events
// Start
Expand Down Expand Up @@ -117,6 +119,7 @@ class FlowableCanvasPopupMenuProvider(private val project: Project) : CanvasPopu
popup.add(intermediateCatchingEvents(sceneLocation, parent))
popup.add(intermediateThrowingEvents(sceneLocation, parent))
popup.add(endEvents(sceneLocation, parent))
addItem(popup, "Save to PNG", SAVE_TO_PNG, ActionListener { saveDiagramToPng(project) })
return popup
}

Expand Down

0 comments on commit 03e8fc9

Please sign in to comment.