Comment dessiner un trait transparent (ou de toute façon une partie claire d'une image) sur l'iPhone
-
08-07-2019 - |
Question
J'ai une petite application qui permet à l'utilisateur de dessiner sur l'écran avec le doigt.
J'ai un UIImageView
où l'utilisateur dessine, en créant un CGContextRef
et les diverses fonctions CG Draw
. Je dessine principalement des traits / des lignes avec la fonction CGContextAddLineToPoint
Maintenant, mon problème est le suivant:
L'utilisateur peut dessiner des lignes de différentes couleurs. Je veux lui donner la possibilité d'utiliser un outil "caoutchouc"
pour supprimer une partie de l'image dessinée jusqu'à présent, avec le doigt. Je l’avais initialement fait en utilisant une couleur blanche pour le trait (défini avec la fonction CGContextSetRGBStrokeColor
), mais cela n’a pas fonctionné ... car j’ai découvert plus tard que le UIImage
sur le UIImageView
, l’arrière-plan était transparent, pas blanc ... je me retrouvais donc avec une image transparente avec des lignes blanches!
Est-il possible de définir une couleur de trait "transparent"
ou existe-t-il un autre moyen d'effacer le contenu du CGContextRef
sous le doigt de l'utilisateur lorsqu'il se déplace il?
Merci
La solution
Cela fera l'affaire:
CGContextSetBlendMode(context, kCGBlendModeClear)
Autres conseils
J'ai fini par utiliser l'algorithme de ligne de Bresenham (pour remonter à l'époque où je devais écrire mes propres routines graphiques) ...
- (void) contextEraseLine:(CGContextRef) ctx from:(CGPoint)startPoint to:(CGPoint) endPoint withThickness:(int)thickness {
int x, cx, deltax, xstep,
y, cy, deltay, ystep,
error, st, dupe;
int x0, y0, x1, y1;
x0 = startPoint.x;
y0 = startPoint.y;
x1 = endPoint.x;
y1 = endPoint.y;
// find largest delta for pixel steps
st = (abs(y1 - y0) > abs(x1 - x0));
// if deltay > deltax then swap x,y
if (st) {
(x0 ^= y0); (y0 ^= x0); (x0 ^= y0); // swap(x0, y0);
(x1 ^= y1); (y1 ^= x1); (x1 ^= y1); // swap(x1, y1);
}
deltax = abs(x1 - x0);
deltay = abs(y1 - y0);
error = (deltax / 2);
y = y0;
if (x0 > x1) { xstep = -1; }
else { xstep = 1; }
if (y0 > y1) { ystep = -1; }
else { ystep = 1; }
for ((x = x0); (x != (x1 + xstep)); (x += xstep))
{
(cx = x); (cy = y); // copy of x, copy of y
// if x,y swapped above, swap them back now
if (st) { (cx ^= cy); (cy ^= cx); (cx ^= cy); }
(dupe = 0); // initialize no dupe
if(!dupe) { // if not a dupe, write it out
//NSLog(@"(%2d, %2d)", cx, cy);
CGContextClearRect(ctx, CGRectMake(cx, cy, thickness, thickness));
}
(error -= deltay); // converge toward end of line
if (error < 0) { // not done yet
(y += ystep);
(error += deltax);
}
}
}
Ouf! Il reste un long chemin à parcourir pour créer une ligne de gomme (quelque peu) maladroite.
Pour l'utiliser, procédez comme suit:
- (void)eraseStart {
// erase lines
UIGraphicsBeginImageContext(drawingBoard.size);
ctx = UIGraphicsGetCurrentContext();
CGContextDrawImage(ctx,CGRectMake(0,0,drawingBoard.size.width, drawingBoard.size.height),[drawingBoard CGImage]);
}
- (void)eraseEnd {
drawingBoard = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[drawingView removeFromSuperview];
[drawingView release];
drawingView = [[UIImageView alloc] initWithImage:drawingBoard];
drawingView.frame = CGRectMake(intEtchX, intEtchY, intEtchWidth, intEtchHeight);
[self.view addSubview:drawingView];
}
Cela suppose que vous avez déjà créé un DrawingView (UIImageView) et un DrawingBoard (UIImage).
Ensuite, pour effacer une ligne, faites simplement quelque chose comme:
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self eraseStart];
[self contextEraseLine:ctx from:CGPointMake (x1, y1) to:CGPointMake (x2, y2) withThickness:10];
[self eraseEnd];
(remplace x1, y1, x2 et y2 par les valeurs appropriées) ...
J'ai essayé d'utiliser:
CGContextSetStrokeColorWithColor (myContext, [[UIColor clearColor] CGColor]);
mais cela ne fonctionne pas, car cela semble être "dessin". en plus du contexte avec une couleur invisible (et une couleur invisible + quelle que soit la couleur sur laquelle il est dessiné = la couleur sur laquelle il est dessiné).
La seule solution que j'ai trouvée (qui ne soit pas optimale) est la suivante:
CGContextClearRect (myContext, CGRectMake(x, y, width, height));
Malheureusement, cela signifie que vous devez tracer une série de phénomènes et générer la ligne vous-même ...
Si vous souhaitez effacer l’image par une touche, c’est mon code .... la partie unerase n’est pas recommandée, mais elle est légèrement lente.
func addNewPathToImage(){
print("Start erasing or not erasing")
UIGraphicsBeginImageContextWithOptions(TopImageUIImageView.bounds.size, false, UIScreen.main.scale) // or 0 for the scale
//My image is aspect fit so I need to get rect
let aspect = TopImageUIImageView.image!.size.width / TopImageUIImageView.image!.size.height
let viewRatio = TopImageUIImageView.bounds.size.width / TopImageUIImageView.bounds.size.height
if aspect < viewRatio {
let scale = TopImageUIImageView.bounds.size.height / TopImageUIImageView.image!.size.height
let width = scale * TopImageUIImageView.image!.size.width
let topLeftX = (TopImageUIImageView.bounds.size.width - width) * 0.5
rect = CGRect(x: topLeftX, y: 0, width: width, height: TopImageUIImageView.bounds.size.height)
}
else {
let scale = TopImageUIImageView.bounds.size.width / TopImageUIImageView.image!.size.width
let height = scale * TopImageUIImageView.image!.size.height
let topLeftY = (TopImageUIImageView.bounds.size.height - height) * 0.5
rect = CGRect(x: 0.0, y: topLeftY, width: TopImageUIImageView.bounds.size.width, height: height)
}
////
context = UIGraphicsGetCurrentContext()
TopImageUIImageView.image?.draw(in: rect)
context?.setLineCap(.round)
context?.setLineWidth(brushSize)
if isErasing == true {
context?.setShadow(offset: CGSize(width: 0, height: 0), blur: blurNumber)
context?.setStrokeColor(UIColor.white.cgColor)
context?.setBlendMode(.clear)
}else{
print("test the unerase image .... ?")
context?.setStrokeColor(UIColor.init(patternImage: topImage.af_imageAspectScaled(toFit: CGSize(width: TopImageUIImageView.bounds.size.width, height: TopImageUIImageView.bounds.size.height))).cgColor)
}
// I am using these because I am using touch to define what to erase
context?.move(to: CGPoint(x: lastTouch.x, y: lastTouch.y))
context?.addLine(to: CGPoint(x: currentTouch.x, y: currentTouch.y))
context?.strokePath()
context?.closePath() // when add this line or the "context?.beginPath" get ERROR CGContextClosePath: no current point.
print("close the path")
//get your image
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
TopImageUIImageView.image = image
}
//erase part
if(mouseSwiped)
{
//**************Working Code*************//
UIGraphicsBeginImageContext(frontImage.frame.size);
[frontImage.image drawInRect:CGRectMake(0, 0, frontImage.frame.size.width, frontImage.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(),kCGImageAlphaNone); //kCGImageAlphaPremultipliedLast);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 10);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1, 0, 0, 10);
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextClearRect (UIGraphicsGetCurrentContext(), CGRectMake(lastPoint.x, lastPoint.y, 10, 10));
CGContextStrokePath(UIGraphicsGetCurrentContext());
frontImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
lastPoint = currentPoint;
mouseMoved++;
if (mouseMoved == 10)
{
mouseMoved = 0;
}
}
UIColor *clearColor = [UIColor clearColor];