Cómo dibujar un trazo transparente (o borrar una parte de una imagen) en el iPhone
-
08-07-2019 - |
Pregunta
Tengo una pequeña aplicación que permite al usuario dibujar en la pantalla con el dedo.
Tengo un UIImageView
donde el usuario dibuja, creando un CGContextRef
y las diversas funciones CG draw
. Principalmente dibujo trazos / líneas con la función CGContextAddLineToPoint
Ahora mi problema es este:
El usuario puede dibujar líneas de varios colores. Quiero darle la posibilidad de usar una herramienta " rubber "
para eliminar una parte de la imagen dibujada hasta ahora, con el dedo. Inicialmente hice esto usando un color blanco para el trazo (configurado con la función CGContextSetRGBStrokeColor
) pero no funcionó ... porque descubrí más tarde que el UIImage
en el UIImageView
en realidad tenía un fondo transparente, no blanco ... ¡así que terminaría con una imagen transparente con líneas blancas!
¿Hay alguna forma de establecer un color de trazo " transparente "
o hay alguna otra forma de borrar el contenido del CGContextRef
bajo el dedo del usuario, cuando se mueve ¿eso?
Gracias
Solución
Esto hará el truco:
CGContextSetBlendMode(context, kCGBlendModeClear)
Otros consejos
Terminé usando el algoritmo de línea de Bresenham (recordando los días de antaño cuando tenía que escribir mis propias rutinas gráficas) ...
- (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);
}
}
}
¡Uf! Es un largo camino por recorrer para crear una línea de borrador (algo) torpe.
Para usarlo, haga algo como:
- (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];
}
Esto supone que ya ha creado un drawingView (UIImageView) y drawingBoard (UIImage).
Luego, para borrar una línea, simplemente haga algo como:
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self eraseStart];
[self contextEraseLine:ctx from:CGPointMake (x1, y1) to:CGPointMake (x2, y2) withThickness:10];
[self eraseEnd];
(reemplace x1, y1, x2 e y2 con los valores apropiados) ...
He intentado usar:
CGContextSetStrokeColorWithColor (myContext, [[UIColor clearColor] CGColor]);
pero eso no funciona, porque parece estar "dibujando" encima del contexto con un color invisible (y color invisible + cualquier color en el que se dibuje = el color en el que se dibuja).
La única solución que he encontrado (que no es óptima) es:
CGContextClearRect (myContext, CGRectMake(x, y, width, height));
Desafortunadamente, eso significa que tienes que rastrear una serie de rectificaciones y generar la línea tú mismo ...
Si desea borrar la imagen con un toque, este es mi código ... la parte de borrar no se recomienda, pero funciona un poco lento.
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];