Align layer or group to selection

Hi, I’ve created a small script that aligns layers to selection in the active document. I could not find that feature in Krita (does it exist already?) so I wrote something that does the job :grinning:. Hopefully it will be useful for others, so I am sharing the script file.

How to use:

  • Paste script in scripter (Main Menu->Tools->Script->Scripter)
  • Make selection (or Ctrl + click on layer, to make selection of specific layer)
  • Select the layer / group you wish to move/align
  • Start script

It should align selected layer/group to selection in the document. It aligns bounding box center (BB) of the selected layer to the BB center of the selection.

Since I am new to Krita, and Python is not “my native tongue” :grinning:, any help where I can find an example or tutorial how to easily integrate the script in Krita UI would be appreciated. I am currently reading Krita docs how to make a plugin. Plugin should have more features, align to top, bottom, left, right of BB selection etc.

Please use it as you wish. (tested in Krita 4.4.8)

align_to_selection

P.S. I am unable to upload Python script file to forum… so I have pasted code…

from krita import *
 
activeDoc = Application.activeDocument()
activeLayer = activeDoc.activeNode()

if activeDoc.selection() is None:
    print("You must have active selection in current document!")
    sys.tracebacklimit = 1
    raise ValueError()    #sys.exit()    #raise SystemExit
    


def getSelectionCenter(currDoc):
    s = currDoc.selection()
    #print("x: " +  str(s.x()) + ", y: " +  str(s.y()) + ", w: " +  str(s.width()) + ", h: " +  str(s.height()))
    coords = [0,0]
    coords[0] = s.x() + round(s.width()/2)
    coords[1] = s.y() + round(s.height()/2)
    #print("selCenterX: " +  str(coords[0]) + ", selCenterY: " +  str(coords[1]))
    return coords

def getBoundsCenter(node):
    coords = [0,0]
    lb = node.bounds()
    coords[0] = lb.x() + round(lb.width()/2)
    coords[1] = lb.y() + round(lb.height()/2)
    return coords

def getMoveCoords(node, sc):
    nc = getBoundsCenter(node)
    mc = [0,0]
    mc[0] = sc[0] - nc[0]
    mc[1] = sc[1] - nc[1]
    return mc


def moveChildren(node, moveCoords, posOffset):
    nChildren = node.childNodes()
    for ch in nChildren:
        #print("ch name: " + ch.name())
        pos = ch.position()
        ch.move(moveCoords[0] + pos.x()+posOffset.x(), moveCoords[1] + pos.y()+posOffset.y())
        moveChildren(ch, moveCoords, posOffset)

selCenter = getSelectionCenter(activeDoc)
moveCoords = getMoveCoords(activeLayer, selCenter)
#print("moveCoords[0]: " +  str(moveCoords[0]) + ", moveCoords[1]: " +  str(moveCoords[1]))

posBeforeMove = activeLayer.position()
activeLayer.move(moveCoords[0]+posBeforeMove.x(), moveCoords[1]+posBeforeMove.y())

moveCoords = getMoveCoords(activeLayer, selCenter)
moveChildren(activeLayer, moveCoords, activeLayer.position()-posBeforeMove)

activeDoc.refreshProjection()
9 Likes

Nice idea!

I use these:

https://api.kde.org/appscomplete-api/krita-apidocs/libs/libkis/html/classKrita.html
https://scripting.krita.org/action-dictionary

https://scripting.krita.org/lessons/introduction

Forums also have a lot of interesting discussions and examples.

Thank you (I will read it also), I have managed to make plug-in version - reading the:

and Qt documentation for UI. Btw. Qt is awesome :slight_smile:

The new version 2.0 as standalone plugin version can be downloaded from:
https://drive.google.com/drive/folders/11MDeI3bHax7KK1rE-A-Ka8MpUCd36AAN?usp=sharing

Plugin version is better since you can position docker wherever you find suitable, and it will be present when you restart Krita.

Install it with: Tools->Scripts->Import Python Plugin

align_to_selection_docker

The version that can be run from scripter:

from PyQt5.QtWidgets import QDialog
from PyQt5 import QtWidgets
from krita import *
import enum

alignCombo = QComboBox()

def createAlignDocker():
    alignCenterButton = QPushButton()
    alignCenterButton.setIcon(Krita.instance().icon('tool_rect_selection')) 
    alignCenterButton.setToolTip("Center")
    alignCenterButton.clicked.connect(b_alignCenter)

    alignHorizButton = QPushButton()
    alignHorizButton.setIcon(Krita.instance().icon('object-align-horizontal-center-calligra')) 
    alignHorizButton.setToolTip("Horizontal")
    alignHorizButton.clicked.connect(b_alignHoriz)

    alignVertButton = QPushButton()
    alignVertButton.setIcon(Krita.instance().icon('object-align-vertical-center-calligra')) 
    alignVertButton.setToolTip("Vertical")
    alignVertButton.clicked.connect(b_alignVert)
    

    alignOtherLabel = QLabel(" | ") 

    alignOtherButton = QPushButton() 
    alignOtherButton.setIcon(Krita.instance().icon('updateColorize'))
    alignOtherButton.setToolTip("Apply selected")
    alignOtherButton.clicked.connect(b_alignCombo)

    #alignCombo = QComboBox()
    #alignCombo.setMaximumWidth(92)
    alignCombo.addItem("Left")
    alignCombo.addItem("Right")
    alignCombo.addItem("Top")
    alignCombo.addItem("Bottom")
    alignCombo.addItem("Top-Left")
    alignCombo.addItem("Top-Right")
    alignCombo.addItem("Bottom-Left")
    alignCombo.addItem("Bottom-Right")

    alignLayout = QHBoxLayout()
    alignLayout.setSpacing(2)
    alignLayout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
    alignLayout.addWidget(alignCenterButton)
    alignLayout.addWidget(alignHorizButton)
    alignLayout.addWidget(alignVertButton)
    alignLayout.addWidget(alignOtherLabel)
    alignLayout.addWidget(alignCombo)
    alignLayout.addWidget(alignOtherButton)


    widget = QWidget()
    widget.setLayout(alignLayout)
    widget.resize(0,0)

    dw = QDockWidget("Align to selection")
    dw.setObjectName("Align2selection")
    dw.setAllowedAreas(Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea | Qt.TopDockWidgetArea | Qt.BottomDockWidgetArea)
    dw.setWidget(widget)
    dw.setMinimumHeight(40)
    dw.resize(0,0)
    Krita.instance().activeWindow().qwindow().addDockWidget(Qt.RightDockWidgetArea, dw)
    dw.setFloating(True)


class EAlignType(enum.Enum):
    center = 0
    horiz = 1
    vert = 2
    comboBox = 3

def getSelectionCenter(currDoc):
    s = currDoc.selection()
    coords = [0,0]
    coords[0] = s.x() + round(s.width()/2)
    coords[1] = s.y() + round(s.height()/2)
    return coords

def getBoundsX(node):
    lb = node.bounds()
    xr = lb.x() + round(lb.width()/2)
    return xr

def getBoundsY(node):
    lb = node.bounds()
    yr = lb.y() + round(lb.height()/2)
    return yr

def getBoundsCenter(node):
    coords = [0,0]
    lb = node.bounds()
    coords[0] = lb.x() + round(lb.width()/2)
    coords[1] = lb.y() + round(lb.height()/2)
    return coords

def getMoveCoords(node, sc):
    nc = getBoundsCenter(node)
    mc = [0,0]
    mc[0] = sc[0] - nc[0]
    mc[1] = sc[1] - nc[1]
    return mc


def moveChildren(node, moveCoords, posOffset):
    nChildren = node.childNodes()
    for ch in nChildren:
        pos = ch.position()
        ch.move(moveCoords[0] + pos.x()+posOffset.x(), moveCoords[1] + pos.y()+posOffset.y())
        moveChildren(ch, moveCoords, posOffset)



def applyAlignType(alignType, mc, currDoc):
    if alignType == EAlignType.horiz:
        mc[1] = 0
        return mc
    if alignType == EAlignType.vert:
        mc[0] = 0
        return mc
    if alignType == EAlignType.comboBox:
        s = currDoc.selection()
        if alignCombo.currentIndex() == 0: #Left
            mc[0] -= round(s.width()/2)
            return mc
        if alignCombo.currentIndex() == 1: #Right
            mc[0] += round(s.width()/2)
            return mc
        if alignCombo.currentIndex() == 2: #Top
            mc[1] -= round(s.height()/2)
            return mc
        if alignCombo.currentIndex() == 3: #Bottom
            mc[1] += round(s.height()/2)
            return mc
        if alignCombo.currentIndex() == 4: #Top-Left
            mc[0] -= round(s.width()/2)
            mc[1] -= round(s.height()/2)
            return mc
        if alignCombo.currentIndex() == 5: #Top-Right
            mc[0] += round(s.width()/2)
            mc[1] -= round(s.height()/2)
            return mc
        if alignCombo.currentIndex() == 6: #Bottom-Left
            mc[0] -= round(s.width()/2)
            mc[1] += round(s.height()/2)
            return mc
        if alignCombo.currentIndex() == 7: #Bottom-Right
            mc[0] += round(s.width()/2)
            mc[1] += round(s.height()/2)
            return mc
    return mc #default center
        


def processAlign(alignType):
    activeDoc = Application.activeDocument()
    activeLayer = activeDoc.activeNode()

    if activeDoc.selection() is None:
        QMessageBox.information(QWidget(), "Align to selection", "You must have active selection in current document!")
        #print("You must have active selection in current document!")
        #sys.tracebacklimit = 1
        #raise ValueError()    #sys.exit()    #raise SystemExit
    else:
        selCenter = getSelectionCenter(activeDoc)
        moveCoords = getMoveCoords(activeLayer, selCenter)
        moveCoords = applyAlignType(alignType, moveCoords, activeDoc)

        posBeforeMove = activeLayer.position()
        activeLayer.move(moveCoords[0]+posBeforeMove.x(), moveCoords[1]+posBeforeMove.y())

        moveCoords = getMoveCoords(activeLayer, selCenter)
        moveCoords = applyAlignType(alignType, moveCoords, activeDoc)
        moveChildren(activeLayer, moveCoords, activeLayer.position()-posBeforeMove)

        activeDoc.refreshProjection()


def b_alignCenter():
    processAlign(EAlignType.center)

def b_alignHoriz():
    processAlign(EAlignType.horiz)

def b_alignVert():
    processAlign(EAlignType.vert)

def b_alignCombo():
    processAlign(EAlignType.comboBox)


createAlignDocker()


P.S. I was unable to edit my first post… to point to this new version.

4 Likes

Krita actually has arrangement functions(alignment and distribution functions)
But right now, they only work for different vector objects available on the same vector layer.
Alignment or distribution between selection/layer/layerGroup and layer/layerGroup is impossible.
I once told the core devs about this on the forum. But they were too busy with lots of other things and wouldn’t bring life to an update on that side before a while.
The current arrangement functions are compatible only with vector data, not raster data.
Yes, Krita has lots of useful tools, but these things which are in my opinion the first important to-date features to image editing, were slightly neglected as the focus is mostly digital painting.
So yeah. Undeniably, nowadays Krita is powerful for painting.
But lacking in some areas when it comes to general image edition.
Still, let’s say that Krita is nevertheless in its first days of glory and relatively new to the professional field as a super optimized and popular art program.
Talking about concurrence on Krita’s side wasn’t even possible in 2011 and the software was less popular and didn’t even have many of the features it possess today to be competitive enough.
It was Krita 3.x. I didn’t like much and preferred Photoshop.
Then Krita 4.x came, and things started to change seriously.
Now another overhaul is coming with Krita 5.x.
Judging from this, you can only expect serious overhaul when the generation of Krita changes.
And I guess the first number in Krita’s version numbering system, marks a particular generation.
A very very heavy overhaul could require two generations and possibly 4 years+ of development.

So, the arrangement functions will know an upgrade by a certain time, as one of the devs, @tiar said. We just have to be patient. And yes, this upgrade will include operations between multiple layers or group of layers, vector and raster types comprised together.

Coming from Photoshop which is a corporate software, I understand they might be more ignoring user feedback and feature requests, but there are certain things that they did well in my opinion.
For instance, the alignment and distribution UI appears only when you pick the move tool, and it dynamically changes functions displayed on the top options bar. The dynamic changes is such that additional options and operations specific to a given particular tool show up in the option bar. Switch the tool and the option bar’s content also changes. I’d call this genuine as it shows that they care about ergonomics quite a lot and consider possible advanced use cases almost all the time.
I wished, presented, discussed this on the forum, wanting to see the same later in Krita’s UI/UX functionalities. But the fact that Adobe have money and more manpower is to be remembered when you see that Krita doesn’t have it all sometimes, just like Photoshop. So, yeah, things are slow on Krita’s development side.
The only solution that could speed things up is getting effectively great to huge sponsorships.
If for instance, millions of US dollars were donated to the Krita foundation for the development of the app,
that’d be an incredible boost. I wonder if Elon Musk could help on that side. How to convince him to
do so. Would he even be willing to give half a million for that ? I’m not sure and I don’t know.

1 Like

I can tell you as someone who installed Krita 5 days ago, for the first time – that Krita, as it is, is 95%+ close to all features that I need on daily basis, compared to PS. I don’t paint, but I edit images and make textures, since I develop games. My daily ecosystem mainly revolves around Unity (C# scripts), Blender and Photoshop.

I found about Krita when I was checking if Gimp finally implemented non-destructive editing. Sadly, not yet.

Non-destructive editing and grouping of image layers is first thing that I need to be productive when making textures (also Krita wraparound mode is way better than PS offset…). I need a lot of tweaking, and simply cannot use effectively software that does destructive operations on image. That was the point when I found about Krita - as being much better than Gimp, and very close to PS (someone even used term Photoshop killer :grinning:).

So, I installed it, and was very surprised how good and useful it is – even though I didn’t have any experience with it, I could work almost instantly since everything is similar to PS workflow.

The align was first thing that I noticed missing. Since if you need to make texture sheet/atlas that has 4x4 grid of images on same texture, that are used in game as animation… you absolutely need to have some sort of align (consider explosion with alpha, that needs to be put in 4x4 grid in one texture, as 16 frames in game, and centered in each square). That is why I wrote this until something better pops up (and I wanted to learn a bit more about python and Qt :grinning:).

Next, I will make pixel grow plugin (as in Blender when you bake textures, and will post it here when done). It is useful when you want to add same pixels on edges of UV map, to avoid artifacts when image is scaled down or resampled. Or when you want to make color part of the transparent image full with color on edges, and use alpha as separate layer/mask (to avoid ghosting in some image formats – effectively creating straight alpha image).

Anyway, I find Krita refreshing – since you can use Python and Qt to create whatever you need. Same as I find Blender incredible – also you can use Python to write what you need, or automate tasks. That kind of modularity is rare, and usually only well written applications can have it. Well done Krita team.

1 Like

Now that it has been completed, it should be in the “Resources” page

Sounds interesting. It seems to be similar to the behavior of adjusting the line thickness in clipstudiospaint. If there is a reduction in pixels, that would be great

If not for the few incomplete features we still have, like the arrangement operators, unavailable chained transform mode, unavailable modifier key behavior with certain transform operations, selections tools which still have bugs, I’d give Krita the medal of Photoshop killer. Regarding painting, yes, Krita sure makes it as a raising competitor on the market, especially when we consider the new upcoming features on that side, which are coming in version 5.0. But in the digital art environment, painting alone isn’t everything, and image editing is precious everywhere for faster workflows in diverse cases. So, things like, vector paths, vector pen tool, alignment, selection, distribution, transformation, HSL settings, Color Balance, Gradient Maps, Layer styles, are essential as well.
One day I wanted to do a digital painting assignment which required to use the pen tool.
But Krita blatantly deceived me. I mistook the pen tool icon for what would be the same as in Photoshop. It’s just Calligraphy. And I don’t know why they added it. Lol. I never used it at all. Not so
useful to me. And while there’s bezier curve and whatnot, none was designed the same as Photoshop’s pen tool. The advanced use cases, which make you use modifier keys, are totally absent as of now. So yeah. Life can be deceiving sometimes. Leaving you with two choices : either you support the development through considerable donations and manage to grow the backers of the project, or you design the feature yourself given that it’s also Open source. Yo get the idea.

PixelGrow - It’s a simple operation (does not have pixel reduction). You basically duplicate layer 4 times, and move each copy pixel up, down, left and right… then merge layers together (with original image on top). Then repeat the process as much as you need, each time using merged layer as start. Nothing fancy, but useful in some cases. In PS I did it with actions - recorded everything I did on some layer, but with Python you can automate it more. Layer has to be transparent in regions in which you wish to growPixels.

1 Like

Please stay on topic

Don’t forget your license on your code too.

The plugin on google drive is updated with some minor error checking code.

2 Likes