There is no context2d.ellipse in GWT, so how can I draw an ellipse? The underlying HTML5 canvas supports it, so I thought I would try and access that using a native method using the following (but it does not work)...
ellipse(context.getCanvas(),(double)x,(double)y,50.,80.,0.,0.,Math.PI*2);
...
public final native void ellipse(CanvasElement e, double x, double y, double rx, double ry, double ro, double sa, double ea)
/*-{
e.getContext("2d").ellipse(x, y, rx, ry, ro, sa, ea, false);
}-*/;
ideas / solutions?
If there is no implementation you can draw it using lines, just needs a bit of trigonometry...
function drawEllipse(x, y, rx, ry) {
ctx.beginPath();
for (i = 0; i <= 360; i++) {
a = i * Math.PI / 180
px = rx * Math.sin(a) + x
py = ry * Math.cos(a) + y
ctx.lineTo(px, py);
}
ctx.stroke();
}
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
drawEllipse(100, 100, 90, 30);
drawEllipse(100, 100, 50, 90);
drawEllipse(100, 100, 50, 25);
<canvas id=canvas width=200 height=200></canvas>
Related
I faced the problem in my Flutter app that I can't draw this kind of a shape for my Slider
What I have now is:
final paint = Paint()
..color = Colors.black
..style = PaintingStyle.fill;
final rect = Rect.fromCircle(center: center, radius: thumbRadius);
final rrect = RRect.fromRectAndRadius(
Rect.fromPoints(
Offset(rect.left - 5, rect.top),
Offset(rect.right + 5, rect.bottom),
),
Radius.circular(thumbRadius + 2),
);
canvas.drawRRect(rrect, paint);
Also, it would be great to change height of all bar, because next code changes only the size after player
SliderTheme(
data: SliderThemeData(
trackHeight: 2,
thumbShape: CustomSliderPlayer(),
),
child: Slider(...)
From the comments it looks like you are not familiar with quadratic bezier curves, they are very simple, I would recommend you to start on a Javascript canvas, they are easier to test that way and logic is the same, we move to the starting point then we draw the curve, see sample snippet below
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
function dobleQuad(x, y, w, h) {
ctx.beginPath();
ctx.moveTo(x - w, y);
ctx.quadraticCurveTo(x, y - h, x + w, y);
ctx.moveTo(x - w, y);
ctx.quadraticCurveTo(x, y + h, x + w, y);
ctx.fill();
}
function drawSlider(x, y) {
ctx.moveTo(0, y - 2);
ctx.fillRect(0, y - 2, canvas.width, 4);
dobleQuad(x, y, 20, 22)
}
drawSlider(50, 50)
canvas.addEventListener('mousemove', function(evt) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
var rect = canvas.getBoundingClientRect();
drawSlider( evt.clientX - rect.left, 50 )
})
<canvas id="canvas"></canvas>
Just keep in mind that in JS it's quadraticCurveTo but in flutter quadraticBezierTo
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/quadraticCurveTo
void ctx.quadraticCurveTo(cpx, cpy, x, y);
cpx, cpy
The coordinates of the control point.
x, y
The coordinates of the end point.
https://api.flutter.dev/flutter/dart-ui/Path/quadraticBezierTo.html
void quadraticBezierTo(
double x1, double y1,
double x2, double y2
)
Adds a quadratic bezier segment that curves from the current point
to the given point (x2,y2), using the control point (x1,y1).
I want to proceduraly generate meshes. I created a method to supply the vertices of a circle. The idea is that it creates a shape in 2d and then rotates it in 3d assuming that "rotation" is the vector of the shapes normal axis.
public List<Vector3> Loop (Vector3 center, Vector3 rotation, float radius, int divisions)
{
List<Vector3> loop = new List<Vector3>();
for(int p = 0; p < divisions; p++)
{
float u = (float)Math.Cos(2 * Math.PI * p / divisions) * radius;
float v = (float)Math.Sin(2 * Math.PI * p / divisions) * radius;
float x = 0;
float y = 0;
float z = 0;
// Apply rotation to u & v to get x, y, z
loop.Add(new Vector3(x, y, z));
}
return loop;
}
Creating the circle in 2d (u & v) was super easy but when I looked into applying 3d rotations, it seemed to be a complete rabbit hole completely beyond my comprehension.
Is there a way to use existing API to do this?
I would pass in an axis parameter that you are rotating rotation around, then use Cross products to find the "up" direction for the "forward" that is the normal of the circle.
Use Quaternion.LookRotation, then Quaternion * Vector3 to apply the rotation to the position:
public List<Vector3> Loop (Vector3 center, Vector3 rotation, Vector3 axis, float radius, int divisions)
{
List<Vector3> loop = new List<Vector3>();
for(int p = 0; p < divisions; p++)
{
float u = (float)Math.Cos(2 * Math.PI * p / divisions) * radius;
float v = (float)Math.Sin(2 * Math.PI * p / divisions) * radius;
Vector3 fromPosition = new Vector3(u, v, 0f);
Vector3 up = Vector3.Cross(rotation.normalized, axis.normalized);
Quaternion rot = Quaternion.LookRotation(rotation, up);
loop.Add(rot * fromPosition);
}
return loop;
}
So you could do something like: List<Vector3> res = Loop(Vector3.zero, Vector3.up, Vector3.right, 10f, 20);
I started using itextsharp 7 a few days ago, i used to work with itextsharp 5 for years .
I don't manage to add a scaled image at the center of the page as watermark with itext7.
My code with itextsharp 5 :
using (PdfStamper pdfStamper = new PdfStamper(pdfReader, memoryStream))
{
for (int pageIndex = 1; pageIndex <= pdfReader.NumberOfPages; pageIndex++)
{
pdfStamper.FormFlattening = false;
iTextSharp.text.Rectangle pageRectangle = pdfReader.GetPageSizeWithRotation(pageIndex);
PdfContentByte pdfData = pdfStamper.GetOverContent(pageIndex);
PdfGState graphicsState = new PdfGState();
graphicsState.FillOpacity = 0.4F;
pdfData.SetGState(graphicsState);
pdfData.BeginText();
Image imageWM = Image.GetInstance(image_WM_Path);
float width = pageRectangle.Width;
float height = pageRectangle.Height;
//scale image
imageWM.ScaleToFit(width / 3, height / 3);
//center image
imageWM.SetAbsolutePosition(width / 2 - imageWM.ScaledWidth / 2, height / 2 - imageWM.ScaledHeight / 2);
pdfData.AddImage(imageWM);
pdfData.EndText();
}
pdfStamper.Close();
return memoryStream.ToArray();
}
Here is with itextsharp 7 (code based on the itext 7 examples):
PdfDocument pdfDoc = new PdfDocument(new PdfReader(sourceFile), new PdfWriter(destinationPath));
Document document = new Document(pdfDoc);
PdfCanvas over;
PdfExtGState gs1 = new PdfExtGState();
gs1.SetFillOpacity(0.5f);
int n = pdfDoc.GetNumberOfPages();
Rectangle pagesize;
float x, y;
ImageData img = ImageDataFactory.Create(image_WM_Path);
float w = img.GetWidth();
float h = img.GetHeight();
for (int i = 1; i <= n; i++)
{
PdfPage pdfPage = pdfDoc.GetPage(i);
pagesize = pdfDoc.GetPage(i).GetPageSize();
pdfPage.SetIgnorePageRotationForContent(true);
x = (pagesize.GetLeft() + pagesize.GetRight()) / 2;
y = (pagesize.GetTop() + pagesize.GetBottom()) / 2;
over = new PdfCanvas(pdfDoc.GetPage(i));
over.SaveState();
over.SetExtGState(gs1);
over.AddImage(img, w, 0, 0, h, x - (w / 2), y - (h / 2), true);
over.RestoreState();
}
document.Close();
pdfDoc.Close();
The image is centered but i dont manage to scale it with the AddImage method.
Maybe it is easily done but i am struggling with this.
Any help appreciated.
I have adapted your example to Java, but that shouldn't matter much since it's the Math that is important:
public static final String SRC = "src/main/resources/pdfs/hello.pdf";
public static final String DEST = "results/text/watermark.pdf";
public static final String IMG = "src/main/resources/img/mascot.png";
public static void main(String[] args) throws IOException {
File file = new File(DEST);
file.getParentFile().mkdirs();
new Watermark().createPdf(SRC, DEST);
}
public void createPdf(String src, String dest) throws IOException {
PdfDocument pdfDoc = new PdfDocument(
new PdfReader(src), new PdfWriter(dest));
Document document = new Document(pdfDoc);
PdfCanvas over;
PdfExtGState gs1 = new PdfExtGState();
gs1.setFillOpacity(0.5f);
int n = pdfDoc.getNumberOfPages();
Rectangle pagesize;
ImageData img = ImageDataFactory.create(IMG);
float iW = img.getWidth();
float iH = img.getHeight();
float pW, pH, sW, sH, f, x, y;
for (int i = 1; i <= n; i++)
{
PdfPage pdfPage = pdfDoc.getPage(i);
pagesize = pdfPage.getPageSize();
pW = pagesize.getWidth();
pH = pagesize.getHeight();
f = (pW / iW) * 0.5f;
sW = iW * f;
sH = iH * f;
x = pagesize.getLeft() + (pW / 2) - (sW / 2);
y = pagesize.getBottom() + (pH / 2) - (sH / 2);
over = new PdfCanvas(pdfDoc.getPage(i));
over.saveState();
over.setExtGState(gs1);
over.addImage(img, sW, 0, 0, sH, x, y);
over.restoreState();
}
document.close();
pdfDoc.close();
}
The result of this code looks like this:
That looks exactly the way I expect it.
Some explanation.
I have an image mascot.png with dimensions iW x iH.
I have pages with dimensions pW x pH.
I want to scale the image so that it takes 50% of the width, hence I create a variable f with value 0.5f (50%) x ``(pW / iW)`.
I apply the factor f to the initial values of the images, resulting in the scaled dimensions sW x sH.
I define an offset for the image (x, y) by subtracting half of the scaled width and height of the middle of the page.
Now I have the values I need for the addImage() method: over.addImage(img, sW, 0, 0, sH, x, y);
Note: you were adding the images as an inline image. That's a bad idea because it leads to bloated PDF files, especially in the case of watermarks. By adding an image as an inline image to each page, you add the image bytes redundantly as many times as there are pages. It's much better to add the image as an Image XObject, in which case the image bytes will be added to the document only once, no matter how many times you use that same image. Please remove the true value from the parameters of the addImage() method (make a before and after PDF, and compare the file size to understand what I mean).
Maybe you can use AddImageFittedIntoRectangle
var x = width / 2 - imageWM.ScaledWidth / 2;
var y = height / 2 - imageWM.ScaledHeight / 2;
var w = width / 3;
var h = height / 3;
over.AddImage(img, new Rectangle(x, y, w, h), false);
I am using itextpdf to merge some pdfs to a single one.
What are the meanings of itextpdf pdfcontentbyte addtemplate's parameters,there is no docs to describe them.
public void addTemplate(PdfTemplate template,
double a, double b, double c, double d, double e, double f)
The six values a, b, c, d, e, and f are elements of a matrix that has three rows and three columns.
You can use this matrix to express a transformation in a two-dimentional system.
Carrying out this multiplication results in this:
x' = a * x + c * y + e
y' = b * x + d * y + f
The third column in the matrix is fixed: you're working in two dimensions, so you don't need to calculate a new z coordinate.
When studying analytical geometry in high school, you've probably learned how to apply transformations to objects.
In PDF, we use a slightly different approach: instead of transforming objects, we transform the coordinate system.
The e and the f value can be used for a translation. The a, b, c, and d value can be used for a rotation and/or scaling operation.
By default the Current Transformation Matrix (CTM) is:
With the addTemplate() method, you can add a Form XObject to a canvas and define a position using e, f, e.g:
canvas.addTemplate(template, 36, 36);
This will add template at coordinate x = 36; y = 36.
By introducing a, b, c, and d, you can also rotate and/or scale the template.
Update: as mentioned in the comments, you might want to use the overloaded methods that accept an AffineTransform parameter if you don't like the Algebra of the transformation matrix.
the code below did the trick,thank for the guys who helped me.
FileInputStream pdfInput = new FileInputStream(pdf);
PdfReader pdfReader = new PdfReader(pdfInput);
for (int index = 1; index <= pdfReader.getNumberOfPages(); index++) {
main.newPage();
PdfImportedPage page = pdfWriter.getImportedPage(pdfReader,
index);
Rectangle pagesize = pdfReader.getPageSizeWithRotation(index);
float oWidth = pagesize.getWidth();
float oHeight = pagesize.getHeight();
float scale = getScale(oWidth, oHeight);
float scaledWidth = oWidth * scale;
float scaledHeight = oHeight * scale;
int rotation = pagesize.getRotation();
AffineTransform transform = new AffineTransform(scale, 0, 0, scale, 0, 0);
switch (rotation) {
case 0:
cb.addTemplate(page, transform);
break;
case 90:
AffineTransform rotate90 = new AffineTransform(0, -1f, 1f, 0, 0, scaledHeight);
rotate90.concatenate(transform);
cb.addTemplate(page, rotate90);
break;
case 180:
AffineTransform rotate180 = new AffineTransform(-1f, 0, 0, -1f, scaledWidth,
scaledHeight);
rotate180.concatenate(transform);
cb.addTemplate(page, rotate180);
break;
case 270:
AffineTransform rotate270 = new AffineTransform(0, 1f, -1f, 0, scaledWidth, 0);
rotate270.concatenate(transform);
cb.addTemplate(page, rotate270);
break;
default:
cb.addTemplate(page, scale, 0, 0, scale, 0, 0);
}
}
private static float getScale(float width, float height) {
float scaleX = PageSize.A4.getWidth() / width;
float scaleY = PageSize.A4.getHeight() / height;
return Math.min(scaleX, scaleY);
}
I need to rotate an image by very small angle, like 1-5 degrees. Does OpenCV provide simple way of doing that? From reading docs i can assume that getAffineTransform() should be involved, but there is no direct example of doing something like:
IplImage *rotateImage( IplImage *source, double angle);
If you use OpenCV > 2.0 it is as easy as
using namespace cv;
Mat rotateImage(const Mat& source, double angle)
{
Point2f src_center(source.cols/2.0F, source.rows/2.0F);
Mat rot_mat = getRotationMatrix2D(src_center, angle, 1.0);
Mat dst;
warpAffine(source, dst, rot_mat, source.size());
return dst;
}
Note: angle is in degrees, not radians.
See the C++ interface documentation for more details and adapt as you need:
getRotationMatrix
warpAffine
Edit: To down voter: Please comment the reason for down voting a tried and tested code?
#include "cv.h"
#include "highgui.h"
#include "math.h"
int main( int argc, char** argv )
{
IplImage* src = cvLoadImage("lena.jpg", 1);
IplImage* dst = cvCloneImage( src );
int delta = 1;
int angle = 0;
int opt = 1; // 1: rotate & zoom
// 0: rotate only
double factor;
cvNamedWindow("src", 1);
cvShowImage("src", src);
for(;;)
{
float m[6];
CvMat M = cvMat(2, 3, CV_32F, m);
int w = src->width;
int h = src->height;
if(opt)
factor = (cos(angle*CV_PI/180.) + 1.05) * 2;
else
factor = 1;
m[0] = (float)(factor*cos(-angle*2*CV_PI/180.));
m[1] = (float)(factor*sin(-angle*2*CV_PI/180.));
m[3] = -m[1];
m[4] = m[0];
m[2] = w*0.5f;
m[5] = h*0.5f;
cvGetQuadrangleSubPix( src, dst, &M);
cvNamedWindow("dst", 1);
cvShowImage("dst", dst);
if( cvWaitKey(1) == 27 )
break;
angle =(int)(angle + delta) % 360;
}
return 0;
}
UPDATE: See the following code for rotation using warpaffine
https://code.google.com/p/opencvjp-sample/source/browse/trunk/cpp/affine2_cpp.cpp?r=48
#include <cv.h>
#include <highgui.h>
using namespace cv;
int
main(int argc, char **argv)
{
// (1)load a specified file as a 3-channel color image,
// set its ROI, and allocate a destination image
const string imagename = argc > 1 ? argv[1] : "../image/building.png";
Mat src_img = imread(imagename);
if(!src_img.data)
return -1;
Mat dst_img = src_img.clone();
// (2)set ROI
Rect roi_rect(cvRound(src_img.cols*0.25), cvRound(src_img.rows*0.25), cvRound(src_img.cols*0.5), cvRound(src_img.rows*0.5));
Mat src_roi(src_img, roi_rect);
Mat dst_roi(dst_img, roi_rect);
// (2)With specified three parameters (angle, rotation center, scale)
// calculate an affine transformation matrix by cv2DRotationMatrix
double angle = -45.0, scale = 1.0;
Point2d center(src_roi.cols*0.5, src_roi.rows*0.5);
const Mat affine_matrix = getRotationMatrix2D( center, angle, scale );
// (3)rotate the image by warpAffine taking the affine matrix
warpAffine(src_roi, dst_roi, affine_matrix, dst_roi.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar::all(255));
// (4)show source and destination images with a rectangle indicating ROI
rectangle(src_img, roi_rect.tl(), roi_rect.br(), Scalar(255,0,255), 2);
namedWindow("src", CV_WINDOW_AUTOSIZE);
namedWindow("dst", CV_WINDOW_AUTOSIZE);
imshow("src", src_img);
imshow("dst", dst_img);
waitKey(0);
return 0;
}
Check my answer to a similar problem:
Rotating an image in C/C++
Essentially, use cvWarpAffine - I've described how to get the 2x3 transformation matrix from the angle in my previous answer.
Updating full answer for OpenCV 2.4 and up
// ROTATE p by R
/**
* Rotate p according to rotation matrix (from getRotationMatrix2D()) R
* #param R Rotation matrix from getRotationMatrix2D()
* #param p Point2f to rotate
* #return Returns rotated coordinates in a Point2f
*/
Point2f rotPoint(const Mat &R, const Point2f &p)
{
Point2f rp;
rp.x = (float)(R.at<double>(0,0)*p.x + R.at<double>(0,1)*p.y + R.at<double>(0,2));
rp.y = (float)(R.at<double>(1,0)*p.x + R.at<double>(1,1)*p.y + R.at<double>(1,2));
return rp;
}
//COMPUTE THE SIZE NEEDED TO LOSSLESSLY STORE A ROTATED IMAGE
/**
* Return the size needed to contain bounding box bb when rotated by R
* #param R Rotation matrix from getRotationMatrix2D()
* #param bb bounding box rectangle to be rotated by R
* #return Size of image(width,height) that will compleley contain bb when rotated by R
*/
Size rotatedImageBB(const Mat &R, const Rect &bb)
{
//Rotate the rectangle coordinates
vector<Point2f> rp;
rp.push_back(rotPoint(R,Point2f(bb.x,bb.y)));
rp.push_back(rotPoint(R,Point2f(bb.x + bb.width,bb.y)));
rp.push_back(rotPoint(R,Point2f(bb.x + bb.width,bb.y+bb.height)));
rp.push_back(rotPoint(R,Point2f(bb.x,bb.y+bb.height)));
//Find float bounding box r
float x = rp[0].x;
float y = rp[0].y;
float left = x, right = x, up = y, down = y;
for(int i = 1; i<4; ++i)
{
x = rp[i].x;
y = rp[i].y;
if(left > x) left = x;
if(right < x) right = x;
if(up > y) up = y;
if(down < y) down = y;
}
int w = (int)(right - left + 0.5);
int h = (int)(down - up + 0.5);
return Size(w,h);
}
/**
* Rotate region "fromroi" in image "fromI" a total of "angle" degrees and put it in "toI" if toI exists.
* If toI doesn't exist, create it such that it will hold the entire rotated region. Return toI, rotated imge
* This will put the rotated fromroi piece of fromI into the toI image
*
* #param fromI Input image to be rotated
* #param toI Output image if provided, (else if &toI = 0, it will create a Mat fill it with the rotated image roi, and return it).
* #param fromroi roi region in fromI to be rotated.
* #param angle Angle in degrees to rotate
* #return Rotated image (you can ignore if you passed in toI
*/
Mat rotateImage(const Mat &fromI, Mat *toI, const Rect &fromroi, double angle)
{
//CHECK STUFF
// you should protect against bad parameters here ... omitted ...
//MAKE OR GET THE "toI" MATRIX
Point2f cx((float)fromroi.x + (float)fromroi.width/2.0,fromroi.y +
(float)fromroi.height/2.0);
Mat R = getRotationMatrix2D(cx,angle,1);
Mat rotI;
if(toI)
rotI = *toI;
else
{
Size rs = rotatedImageBB(R, fromroi);
rotI.create(rs,fromI.type());
}
//ADJUST FOR SHIFTS
double wdiff = (double)((cx.x - rotI.cols/2.0));
double hdiff = (double)((cx.y - rotI.rows/2.0));
R.at<double>(0,2) -= wdiff; //Adjust the rotation point to the middle of the dst image
R.at<double>(1,2) -= hdiff;
//ROTATE
warpAffine(fromI, rotI, R, rotI.size(), INTER_CUBIC, BORDER_CONSTANT, Scalar::all(0));
//& OUT
return(rotI);
}
IplImage* rotate(double angle, float centreX, float centreY, IplImage* src, bool crop)
{
int w=src->width;
int h=src->height;
CvPoint2D32f centre;
centre.x = centreX;
centre.y = centreY;
CvMat* warp_mat = cvCreateMat(2, 3, CV_32FC1);
cv2DRotationMatrix(centre, angle, 1.0, warp_mat);
double m11= cvmGet(warp_mat,0,0);
double m12= cvmGet(warp_mat,0,1);
double m13= cvmGet(warp_mat,0,2);
double m21= cvmGet(warp_mat,1,0);
double m22= cvmGet(warp_mat,1,1);
double m23= cvmGet(warp_mat,1,2);
double m31= 0;
double m32= 0;
double m33= 1;
double x=0;
double y=0;
double u0= (m11*x + m12*y + m13)/(m31*x + m32*y + m33);
double v0= (m21*x + m22*y + m23)/(m31*x + m32*y + m33);
x=w;
y=0;
double u1= (m11*x + m12*y + m13)/(m31*x + m32*y + m33);
double v1= (m21*x + m22*y + m23)/(m31*x + m32*y + m33);
x=0;
y=h;
double u2= (m11*x + m12*y + m13)/(m31*x + m32*y + m33);
double v2= (m21*x + m22*y + m23)/(m31*x + m32*y + m33);
x=w;
y=h;
double u3= (m11*x + m12*y + m13)/(m31*x + m32*y + m33);
double v3= (m21*x + m22*y + m23)/(m31*x + m32*y + m33);
int left= MAX(MAX(u0,u2),0);
int right= MIN(MIN(u1,u3),w);
int top= MAX(MAX(v0,v1),0);
int bottom= MIN(MIN(v2,v3),h);
ASSERT(left<right&&top<bottom); // throw message?
if (left<right&&top<bottom)
{
IplImage* dst= cvCreateImage( cvGetSize(src), IPL_DEPTH_8U, src->nChannels);
cvWarpAffine(src, dst, warp_mat/*, CV_INTER_LINEAR + CV_WARP_FILL_OUTLIERS, cvScalarAll(0)*/);
if (crop) // crop and resize to initial size
{
IplImage* dst_crop= cvCreateImage(cvSize(right-left, bottom-top), IPL_DEPTH_8U, src->nChannels);
cvSetImageROI(dst,cvRect(left,top,right-left,bottom-top));
cvCopy(dst,dst_crop);
cvReleaseImage(&dst);
cvReleaseMat(&warp_mat);
//ver1
//return dst_crop;
// ver2 resize
IplImage* out= cvCreateImage(cvSize(w, h), IPL_DEPTH_8U, src->nChannels);
cvResize(dst_crop,out);
cvReleaseImage(&dst_crop);
return out;
}
else
{
/*cvLine( dst, cvPoint(left,top),cvPoint(left, bottom), cvScalar(0, 0, 255, 0) ,1,CV_AA);
cvLine( dst, cvPoint(right,top),cvPoint(right, bottom), cvScalar(0, 0, 255, 0) ,1,CV_AA);
cvLine( dst, cvPoint(left,top),cvPoint(right, top), cvScalar(0, 0, 255, 0) ,1,CV_AA);
cvLine( dst, cvPoint(left,bottom),cvPoint(right, bottom), cvScalar(0, 0, 255, 0) ,1,CV_AA);*/
cvReleaseMat(&warp_mat);
return dst;
}
}
else
{
return NULL; //assert?
}
}