Filter on Calayer Except for a Shape Which Is an Union of (Non Necessarily Distinct) Rectangles

Filter on CALayer except for a shape which is an union of (non necessarily distinct) rectangles

AFAIK, you can't mask a layer to the inverse of a path.

A couple of observations:

  1. If you were just trying to knock out the paths inside the whole view, you could do that with the trick of creating a path that consists of the whole CGRect plus the various interior paths and leverage the even/odd winding/fill rule, but that won't work if your interior paths overlap with each other.

  2. You can mask images to the inverse of a path (by creating a separate "image mask"), but that won't work for dynamic CALayer masking. It's used for masking a NSImage. So, if you were OK using a snapshot for the filtered part of the view, that's an option.

    See code snippet below for example of using image masks.

  3. Another approach is to apply your filter to the whole view, snapshot it, put that snapshot underneath the view in question and then mask the top level view to your interior paths. In effect, mask the un-filtered view to your interior paths, revealing a filtered snapshot of your view below it.

  4. Yet approach would be to create a path representing the outline of the union of all of your interior paths. If the paths are simple (e.g. non-rotated rectangles) this is pretty easy. If the paths are complex (some rotated, some non-rectangular paths, etc.), this gets hairy. But the trivial scenario isn't too bad. Anyway, if you do that, then you can fall back to that even-odd trick.

I'm not wholly satisfied with any of these approaches, but I don't see any other way to accomplish what you're looking for. Hopefully someone will suggest some better ways to tackle this.


To expand on option 2 (using an image mask created by drawing a few paths, possibly overlapping), in Swift 3 you can do something like:

private func maskImageWithPaths(image: NSImage, size: CGSize) -> NSImage {
// define parameters for two overlapping paths

let center1 = CGPoint(x: size.width * 0.40, y: size.height / 2)
let radius1 = size.width * 0.20

let center2 = CGPoint(x: size.width * 0.60 , y: size.height / 2)
let radius2 = size.width * 0.20

// create these two overlapping paths

let path = CGMutablePath()
path.move(to: center1)
path.addArc(center: center1, radius: radius1, startAngle: -.pi / 2, endAngle: .pi * 3 / 2, clockwise: false)
path.move(to: center2)
path.addArc(center: center2, radius: radius2, startAngle: -.pi / 2, endAngle: .pi * 3 / 2, clockwise: false)

// create image from these paths

let imageRep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(size.width), pixelsHigh: Int(size.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSDeviceRGBColorSpace, bitmapFormat: .alphaFirst, bytesPerRow: 4 * Int(size.width), bitsPerPixel: 32)!
let context = NSGraphicsContext(bitmapImageRep: imageRep)!

context.cgContext.addPath(path)
context.cgContext.setFillColor(NSColor.blue.cgColor)
context.cgContext.fillPath()
let maskImage = context.cgContext.makeImage()!

let mask = CGImage(maskWidth: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: 4 * Int(size.width), provider: maskImage.dataProvider!, decode: nil, shouldInterpolate: true)!

let finalImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)!.masking(mask)!
return NSImage(cgImage: finalImage, size: size)
}

That yields:

masked image

This masks out the drawn paths.

macOS draw a rectangle with two holes inside

You don't have to use Core Graphics. You can create a NSView subclass and just stroke/fill the path in draw(_:). In Swift 3:

class HolyView: NSView {

override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)

let path = ... // build the `NSBezierPath` however you want

NSColor.blue.setFill()
path.fill()
}

}

You can then add that view programmatically, or you can make it @IBDesignable and add it directly on your storyboard.

Sample Image

Get bounds of filters applied to Flash Sprite within Sprite

Oh sweet success! (and thanks for the tips) A friend helped solve the problem with a nice recursive function to handle the filters which may exist on nested sprites:

private function getDisplayObjectRectangle(container:DisplayObjectContainer, processFilters:Boolean):Rectangle {
var final_rectangle:Rectangle = processDisplayObjectContainer(container, processFilters);

// translate to local
var local_point:Point = container.globalToLocal(new Point(final_rectangle.x, final_rectangle.y));
final_rectangle = new Rectangle(local_point.x, local_point.y, final_rectangle.width, final_rectangle.height);

return final_rectangle;
}

private function processDisplayObjectContainer(container:DisplayObjectContainer, processFilters:Boolean):Rectangle {
var result_rectangle:Rectangle = null;

// Process if container exists
if (container != null) {
var index:int = 0;
var displayObject:DisplayObject;

// Process each child DisplayObject
for(var childIndex:int = 0; childIndex < container.numChildren; childIndex++){
displayObject = container.getChildAt(childIndex);

//If we are recursing all children, we also get the rectangle of children within these children.
if (displayObject is DisplayObjectContainer) {

// Let's drill into the structure till we find the deepest DisplayObject
var displayObject_rectangle:Rectangle = processDisplayObjectContainer(displayObject as DisplayObjectContainer, processFilters);

// Now, stepping out, uniting the result creates a rectangle that surrounds siblings
if (result_rectangle == null) {
result_rectangle = displayObject_rectangle.clone();
} else {
result_rectangle = result_rectangle.union(displayObject_rectangle);
}
}
}

// Get bounds of current container, at this point we're stepping out of the nested DisplayObjects
var container_rectangle:Rectangle = container.getBounds(container.stage);

if (result_rectangle == null) {
result_rectangle = container_rectangle.clone();
} else {
result_rectangle = result_rectangle.union(container_rectangle);
}

// Include all filters if requested and they exist
if ((processFilters == true) && (container.filters.length > 0)) {
var filterGenerater_rectangle:Rectangle = new Rectangle(0,0,result_rectangle.width, result_rectangle.height);
var bmd:BitmapData = new BitmapData(result_rectangle.width, result_rectangle.height, true, 0x00000000);

var filter_minimumX:Number = 0;
var filter_minimumY:Number = 0;

var filtersLength:int = container.filters.length;
for (var filtersIndex:int = 0; filtersIndex < filtersLength; filtersIndex++) {
var filter:BitmapFilter = container.filters[filtersIndex];

var filter_rectangle:Rectangle = bmd.generateFilterRect(filterGenerater_rectangle, filter);

filter_minimumX = filter_minimumX + filter_rectangle.x;
filter_minimumY = filter_minimumY + filter_rectangle.y;

filterGenerater_rectangle = filter_rectangle.clone();
filterGenerater_rectangle.x = 0;
filterGenerater_rectangle.y = 0;

bmd = new BitmapData(filterGenerater_rectangle.width, filterGenerater_rectangle.height, true, 0x00000000);
}

// Reposition filter_rectangle back to global coordinates
filter_rectangle.x = result_rectangle.x + filter_minimumX;
filter_rectangle.y = result_rectangle.y + filter_minimumY;

result_rectangle = filter_rectangle.clone();
}
} else {
throw new Error("No displayobject was passed as an argument");
}

return result_rectangle;
}

Detect Door Shape in Floor plan using C#

Sorry for the python code. But perhaps this will help solve your problem.
See comments.

import cv2 

img = cv2.imread('NHoXn.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# convert to binary image
thresh=cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY )[1]

# Morphological reconstruction (delete labels)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7,7))
kernel2 = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
marker = cv2.dilate(thresh,kernel,iterations = 1)
while True:
tmp=marker.copy()
marker=cv2.erode(marker, kernel2)
marker=cv2.max(thresh, marker)
difference = cv2.subtract(tmp, marker)
if cv2.countNonZero(difference) == 0:
break

# only walls
se=cv2.getStructuringElement(cv2.MORPH_RECT, (4,4))
walls=cv2.morphologyEx(marker, cv2.MORPH_CLOSE, se)
walls=cv2.erode(walls, kernel2,iterations=2)

# other objects
other=cv2.compare(marker,walls, cv2.CMP_GE)
other=cv2.bitwise_not(other)

# find connected components and select by size and area
output = cv2.connectedComponentsWithStats(other, 4, cv2.CV_32S)
num_labels = output[0]
labels = output[1]
stats=output[2]
centroids = output[3]
for i in range(num_labels):
left,top,width,height,area=stats[i]
if abs(width-40)<12 and abs(height-40)<12 and area>85:
cv2.rectangle(img,(left, top), (left+width, top+height), (0,255,0))

cv2.imwrite('doors.png', img)

Result:
Sample Image

  1. What is shown in the drawing: walls, doors, windows, furniture, text labels.
  2. The doors to be found always touch the walls.
  3. How are walls different from other objects? Thick, these lines are bold. Thus, dilatation with the desired structural element can leave only parts of the walls. And then, by morphological reconstruction, restore the walls together with the elements that concern them: doors, windows in the first place. The drawing will be cleaned of everything that does not touch the walls.
  4. If dilatation and then erosion are further done, then only walls will remain, thin elements, like windows and doors will disappear.
  5. Subtracting (or logical operations) from the third stage the fourth we get a picture that contains only doors, windows and furniture that touched the walls.
  6. What is the difference in the drawing of the door from the windows? The fact that their BB is almost square, the size is approximately the same for all doors in this drawing, their length is approximately equal to r*(1+pi/4).
    Further in the code there is a selection for such signs. At this stage, you can add some more signs that will more accurately separate the doors from other elements.


Related Topics



Leave a reply



Submit