I have a NSView which contains many CALayers. When a user is editing a document, these CALayers animate all edits. I am attempting to implement printing for my app, but I am having some problems printing these CALayers correctly.
Some CALayers bounds occupy the entire NSView, and do not need to be laid out, because their position never changes. However, I also have one CALayer which contains about 20 small CALayers. These CALayers animate their position changes during normal editing. However, when attempting to print the NSView, these small CALayers never get laid out correctly. I am wondering if there is something special I have to do to ensure that these layers are positioned correctly, and allow the NSView to be drawn/printed correctly.
Does anyone have experience printing a Core Animation backed NSView? Any suggestions are appreciated.
In order to work around layout issues, as well as the fact that using -renderInContext: to draw a layer hierarchy does not preserve vector elements, we subclassed CALayer in the Core Plot framework. The CPLayer subclass overrides the default -drawInContext: method to call our custom -renderAsVectorInContext: method (where we do all of our Core Graphics drawing for a layer). To generate a PDF context (or similar) for printing, we then call a custom method with the following code:
-(void)recursivelyRenderInContext:(CGContextRef)context
{
// render self
CGContextSaveGState(context);
[self applyTransform:self.transform toContext:context];
self.renderingRecursively = YES;
if ( !self.masksToBounds ) {
CGContextSaveGState(context);
}
[self renderAsVectorInContext:context];
if ( !self.masksToBounds ) {
CGContextRestoreGState(context);
}
self.renderingRecursively = NO;
// render sublayers
for ( CALayer *currentSublayer in self.sublayers ) {
CGContextSaveGState(context);
// Shift origin of context to match starting coordinate of sublayer
CGPoint currentSublayerFrameOrigin = currentSublayer.frame.origin;
CGRect currentSublayerBounds = currentSublayer.bounds;
CGContextTranslateCTM(context,
currentSublayerFrameOrigin.x - currentSublayerBounds.origin.x,
currentSublayerFrameOrigin.y - currentSublayerBounds.origin.y);
[self applyTransform:self.sublayerTransform toContext:context];
if ( [currentSublayer isKindOfClass:[CPLayer class]] ) {
[(CPLayer *)currentSublayer recursivelyRenderInContext:context];
} else {
if ( self.masksToBounds ) {
CGContextClipToRect(context, currentSublayer.bounds);
}
[currentSublayer drawInContext:context];
}
CGContextRestoreGState(context);
}
CGContextRestoreGState(context);
}
This goes through and renders each layer onto a flat Core Graphics context, preserving position, rotation, and other transforms while rendering all elements as sharp vectors.
One other thing to watch out for when trying to render layers is that the state of your presentation layer hierarchy may not be the same as your internal layer hierarchy. You may have animations that have been applied to move your layers, but the layers' position properties may not have been changed to match. In that case, you should make sure that you either animate the properties themselves, so that the values always stay in sync, or set the values in your layer once the animations have completed.
Last I looked into it, it was not possible to properly print CALayers. It seemed to me at the time that Core Animation was designed for screen only and not for print (which seems consistent with the fact that it was designed initially for iPhone).
I'd love to know I'm wrong.
Related
I'd like to use custom drawing within a Gtk::Layout. That is, I'm using the C++ bindings for Gtk3 (GTKmm 3.14.0), and I have embedded widgets placed on the "canvas", on top of my custom drawing. Basically this works just fine.
Now the problem is related to scrolling. Gtk::Layout can be placed into a Gtk::ScrolledWindow, and when the scrollable area is set to something larger than the visible allocation, scrollbars will show up. Unfortunately, those scrollbars influence only the placement of the embedded widgets, while my custom drawing remains at a fixed position within the window.
This means, both the Gtk::Allocation and the cairo context seem to be related to precisely the visible area, not to the extended virtual "canvas". I could work around that problem by accessing the adjustments from the scrollbars and then translate the cairo context accordingly...
My question is:
is this the proper way to handle such a scrollable drawing?
or is there some way to let the framework do this work for me?
Judging from the source code of gtk+3.0-3.14.5 (which is in Debian/Stable), the Gtk::Layout does nothing to adjust the drawing context. It just invokes the inherited draw() function from GtkWidget. On the other hand, Gtk::Layout is a full-blown container (it inherits from Gtk::Container), and it is scrollable, which together means that it handles gtk_layout_size_allocate() by passing a suitable allocation (screen area) to each of the embedded child widgets -- and in this respect it does handle the moving and clipping related to scrolling the virtual canvas (calls gdk_window_move_resize()).
Thus, if we want to combine the embedded child widgets with custom drawing, we need to bridge this discrepancy manually. This is quite easy actually: all we need to do is to look into the Gtk::Adjusments corresponding to the scrollbars. Because the value of these adjusments is precisely the upper left corner of the visible viewport. Now, if we want our custom drawing to use absolute canvas coordinates, we just have to translate() the given Cairo context. Beware: it is important to save() the state and to restore() it to pristine state when done, otherwise those translations will accumulate.
Here is some example code to demonstrate this custom drawing
we derive a custom container class called Canvas from Gtk::Layout
we override the on_draw() handler, because only there all size allocation to embedded child widgets have been processed
Layering: child widgets are always drawn in the order they have been added to the Gtk::Layout container. Any custom drawing done before invoking the inherited on_draw() function will be below those widgets; any drawing done afterwards will happen on top of them.
if necessary, we can use the foreach(callback) mechanism to visit all child widgets to find out their current position and extension
void
Canvas::determineExtension()
{
if (not recalcExtension_) return;
uint extH=20, extV=20;
Gtk::Container::ForeachSlot callback
= [&](Gtk::Widget& chld)
{
auto alloc = chld.get_allocation();
uint x = alloc.get_x();
uint y = alloc.get_y();
x += alloc.get_width();
y += alloc.get_height();
extH = max (extH, x);
extV = max (extV, y);
};
foreach(callback);
recalcExtension_ = false;
set_size (extH, extV); // define extension of the virtual canvas
}
bool
Canvas::on_draw(Cairo::RefPtr<Cairo::Context> const& cox)
{
if (shallDraw_)
{
uint extH, extV;
determineExtension();
get_size (extH, extV);
auto adjH = get_hadjustment();
auto adjV = get_vadjustment();
double offH = adjH->get_value();
double offV = adjV->get_value();
cox->save();
cox->translate(-offH, -offV);
// draw red diagonal line
cox->set_source_rgb(0.8, 0.0, 0.0);
cox->set_line_width (10.0);
cox->move_to(0, 0);
cox->line_to(extH, extV);
cox->stroke();
cox->restore();
// cause child widgets to be redrawn
bool event_is_handled = Gtk::Layout::on_draw(cox);
// any drawing which follows happens on top of child widgets...
cox->save();
cox->translate(-offH, -offV);
cox->set_source_rgb(0.2, 0.4, 0.9);
cox->set_line_width (2.0);
cox->rectangle(0,0, extH, extV);
cox->stroke();
cox->restore();
return event_is_handled;
}
else
return Gtk::Layout::on_draw(cox);
}
I'm trying out some styling of NSTextFields to get Single Line Text Fields like the Material Design ones. With normal NSTextFields I have no problems, it works out pretty well. I'm subclassing the NSTextFieldCell and draw my custom cell (simply with a 1px border at the bottom).
The code is like this:
override func drawWithFrame(cellFrame: NSRect, inView controlView: NSView) {
//let path:NSBezierPath = NSBezierPath.init(rect: cellFrame)
//path.fill()
var customRect = cellFrame
customRect.origin.y = cellFrame.size.height - 1
customRect.origin.x = 0;
let usedColor:NSColor = NSColor.grayColor()
usedColor.setFill()
let path = NSBezierPath.init(rect: customRect)
path.fill()
super.drawWithFrame(cellFrame, inView: controlView);
}
I'm adding the subclass in the interface builder and assign it to my TextField(Cell). Everything works fine with normal textfields.
But now I want to do the same with NSSecureTextFields, but the outcome is weird.
The focus ring is visible, even though I set it to NONE.
The source code of the NSSecureTextFieldCell is the same as the one above (of course with the difference that I subclassed NSSecureTextFieldCell and not NSTextFieldCell), but why doesn't it show me the line at the bottom of the cell? And why do I get the damn focus ring when I assign my CustomCell-Class to the Cell? I just don't understand it and that makes me nuts.
Download Xcode Project File here (36 KB)
You can use the following code to remove the focus ring in the viewDidLoad
self.SecuredTextField.focusRingType = NSFocusRingType.None
I had the same issue and I solved it by using a standard NSTextField with a NSSecureTextFieldCell subclass as its cell class.
I found a class called ClippingNode that I can use on sprites to only display a specified rectangular area: https://github.com/njt1982/ClippingNode
One problem is that I need to do exactly the opposite, meaning I want the inverse of that. I want everything outside of the specified rectangle to be displayed, and everything inside to be taken out.
In my test I'm using a position of a sprite, which will update frame, so that will need to happen to meaning that new clipping rect will be defined.
CGRect menuBoundaryRect = CGRectMake(lightPuffClass.sprite.position.x, lightPuffClass.sprite.position.y, 100, 100);
ClippingNode *clipNode = [ClippingNode clippingNodeWithRect:menuBoundaryRect];
[clipNode addChild:darkMapSprite];
[self addChild:clipNode z:100];
I noticed the ClippingNode class allocs inside but I'm not using ARC (project too big and complex to update to ARC) so I'm wondering what and where I'll need to release too.
I've tried a couple of masking classes but whatever I mask fits over the entire sprite (my sprite covers the entire screen. Additionally the mask will need to move, so I thought glscissor would be a good alternative if I can get it to do the inverse.
You don't need anything out of the box.
You have to define a CCClippingNode with a stencil, and then set it to be inverted, and you're done. I added a carrot sprite to show how to add sprites in the clipping node in order for it to be taken into account.
#implementation ClippingTestScene
{
CCClippingNode *_clip;
}
And the implementation part
_clip = [[CCClippingNode alloc] initWithStencil:[CCSprite spriteWithImageNamed:#"white_board.png"]];
_clip.alphaThreshold = 1.0f;
_clip.inverted = YES;
_clip.position = ccp(self.boundingBox.size.width/2 , self.boundingBox.size.height/2);
[self addChild:_clip];
_img = [CCSprite spriteWithImageNamed:#"carrot.png"];
_img.position = ccp(-10.0f, 0.0f);
[_clip addChild:_img];
You have to set an extra flag for this to work though, but Cocos will spit out what you need to do in the console.
I once used CCScissorNode.m from https://codeload.github.com/NoodlFroot/ClippingNode/zip/master
The implementation (not what you are looking for the inverse) was something :
CGRect innerClippedLayer = CGRectMake(SCREENWIDTH/14, SCREENHEIGHT/6, 275, 325);
CCScissorNode *tmpLayer = [CCScissorNode scissorNodeWithRect:innerClippedLayer];
[self addChild:tmpLayer];
So for you it may be like if you know the area (rectangle area that you dont want to show i.e. inverse off) and you know the screen area then you can deduct the rectangle are from screen area. This would give you the inverse area. I have not did this. May be tomorrow i can post some code.
I want to change the perspective of a UIView that is in my Viewcontroller. I think that I have to transform this UIView layer, but I don't know how.
I've tried the following code but it is not working
UIView *myView = [[self subviews] objectAtIndex:0];
CALayer *layer = myView.layer;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / -500;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, 45.0f * M_PI / 180.0f, 0.0f, 1.0f, 0.0f);
layer.transform = rotationAndPerspectiveTransform;
I've also tried with the following code:
-(void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGAffineTransform transform = CGAffineTransformIdentity;
transform.b = -0.1;
transform.a = 0.9;
CGContextConcatCTM(ctx,transform);
// do drawing on the context
}
and this too:
CALayer *layerA = [[self.view.layer sublayers] objectAtIndex:0];
layerA.transform = CATransform3DConcat(layerA.transform, CATransform3DMakeRotation(DEGREES_TO_RADIANS(45),1.0,0.0,0.0)
Neither of them worked. How can I change the perspective of a UIView?
In other words, I will put an example. Image this sample code, a rotation Pie RotationPie sample. I would like to change the perpective of it, in the x or z asis.
Your first solution works on my end. It appears like this:
Can You show your whole class code, if it doesn't work the same on your end?
EDIT
Ok, I've reconfigured provided code example, to show how it is possible:
(download here updated code example :http://www.speedyshare.com/dz469/download/Wheel-demo.zip)
And it looks like this:
I am only applying transformation to base subview. All views that are as subviews to that view, will be transformed as well. If You want corresponding subview to have different transformation - it will be harder, because, then You must take in consideration parent view transformation, to calculate new one - it can get really difficult.
But I've done some simple - multi-view level transformations. For example - to achieve effect, that view scales, moves, and rotates:
I've applied movement transformation to parentView
I've applied rotation transformation to parentViews first subview;
I've applied scale transformation to parentViews first subviews subview.
EDIT
Ok, I've reconfigured provided code example, to show how it is possible, in order to leave wheel in transformed position:
(download here updated code example :
http://www.speedyshare.com/5d8Xq/download/Wheel-demo2.zip )
Problem was - in this case, I was adding transformation to wheel itself - and it appears, that Wheel is based on transformations also. Therefore- when You touched it - it replaced existing transformations and applied it's own (to rotate arrows when user swipes wheel).
So - to leave it in perspective while we interact with it - we need another view layer.
I created a new View (lets call it parent view), and added wheel as a subview to this view.
Then I apply transformation to parent View instead of wheel. And it works !
I Hope it helps and You understand now more about transformations :)
I am learning QT and I am puzzled by the difference in performance of QLabel and QGraphics view while panning.
I read a huge 36Mpixels (D800) jpeg file into either QLabel or QGraphics objects and try to drag the full scale image with QLabel/Graphics. Surprisingly, the QLabel provides really smooth movement while QGRaphicsView panning is jerky.
The simplified QGraphicsView code is:
QApplication::setGraphicsSystem("raster");
...
QGraphicsView view();
view.setDragMode(QGraphicsView::ScrollHandDrag);
view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view.setFrameStyle(QFrame::NoFrame);
view.showFullScreen();
QGraphicsPixmapItem *pmItem = new QGraphicsPixmapItem(pixMap);
scene.addItem(pmItem); // only one item in the scene
//view.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); // no difference
view.show();
The simplified QLabel based code is:
void MyQLabel::mouseMoveEvent(QMouseEvent *event){
if(_leftButtonPressed) {
// calculate new {_x, _y} position
repaint();
}
} else super::mouseMoveEvent(event);
}
void MyQLabel::paintEvent(QPaintEvent *aEvent){
QPainter paint(this);
paint.drawPixmap(_x, _y, pixMap);
}
... // Somewhere in the code:
MyQLabel _myLabel(NULL);
_myLabel.showFullScreen();
_myLabel.show();
It feels like QGraphicsView is skipping the over some positions (with fast dragging), while QLabel paints at all intermediate images.
What do I miss?
Thank you
Alex
Likely, the QGraphicsView calls update() when a scroll change is detected.
While your label calls repaint().
The difference is that update() schedules a call to repaint() and multiple rapid calls to update() may call repaint() a single time.
When fast dragging, you might register multiple mouse events in a short time frame. The QGraphicsView will handle all of them and for each of them call update() and only once they are all processed repaint() is actually called.
Your label will force a repaint() for each mouse event.
Your label may be smoother than the graphics view, but it will consume more resources, and on limited hardware the label will lag behind the mouse cursor as the hardware is trying to process all the repaints.