/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % W r i t e I N F O I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % WriteINFOImage() writes image descriptive info to stdout. % % The format of the WriteINFOImage method is: % % MagickPassFail WriteINFOImage(const ImageInfo *image_info,Image *image) % % A description of each parameter follows. % % o image_info: The image info. % % o image: The image. % % */ static MagickPassFail WriteINFOImage(const ImageInfo *image_info,Image *image) { MagickPassFail status; FILE *file; Image *list_entry; char temporary_filename[MaxTextExtent]; const char *format; assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickSignature); assert(image != (Image *) NULL); assert(image->signature == MagickSignature); /* Obtain optional 'identify' style output specification. */ format=AccessDefinition(image_info,"info","format"); if (format != (char *) NULL) (void) LogMagickEvent(CoderEvent,GetMagickModule(), "info:format=\"%s\"",format); /* Open blob. */ status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception); if (status == MagickFail) ThrowWriterException(FileOpenError,UnableToOpenFile,image); /* Allocate and open a temporary file to write output to. */ temporary_filename[0]='\0'; if ((file=GetBlobFileHandle(image)) == (FILE *) NULL) { if(!AcquireTemporaryFileName(temporary_filename)) ThrowWriterException(FileOpenError,UnableToCreateTemporaryFile,image); if ((file=fopen(temporary_filename,"w")) == (FILE *) NULL) { (void) LiberateTemporaryFile(temporary_filename); ThrowWriterException(FileOpenError,UnableToCreateTemporaryFile,image); } } list_entry=image; while (list_entry != (Image *) NULL) { /* Avoid convert style output syntax by restoring original filename. */ (void) strlcpy(list_entry->filename,list_entry->magick_filename, sizeof(list_entry->filename)); /* Describe image. */ if (format != (char *) NULL) { char *text; text=TranslateText(image_info,list_entry,format); if (text != (char *) NULL) { (void) fputs(text,file); (void) fputs("\n",file); MagickFreeMemory(text); } } else { if ((status=DescribeImage(list_entry,file,image_info->verbose)) == MagickFail) break; } list_entry=GetNextImageInList(list_entry); } if ('\0' != temporary_filename[0]) { /* Close temporary file. */ (void) fclose(file); /* Send content of temporary file to blob stream. */ if (WriteBlobFile(image,temporary_filename) == MagickFail) status=MagickFail; (void) LiberateTemporaryFile(temporary_filename); } CloseBlob(image); return status; }
/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % W r i t e J P 2 I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Method WriteJP2Image writes an image in the JPEG 2000 image format. % % JP2 support originally written by Nathan Brown, [email protected] % % The format of the WriteJP2Image method is: % % MagickPassFail WriteJP2Image(const ImageInfo *image_info,Image *image) % % A description of each parameter follows. % % o status: Method WriteJP2Image return MagickTrue if the image is written. % MagickFalse is returned is there is a memory shortage or if the image file % fails to write. % % o image_info: Specifies a pointer to a ImageInfo structure. % % o image: A pointer to an Image structure. % % */ static MagickPassFail WriteJP2Image(const ImageInfo *image_info,Image *image) { char magick[MaxTextExtent], option_keyval[MaxTextExtent], *options = NULL; int format; long y; jas_image_cmptparm_t component_info; jas_image_t *jp2_image; jas_matrix_t *jp2_pixels; jas_stream_t *jp2_stream; register const PixelPacket *p; register int x; unsigned int rate_specified=False, status; int component, number_components; unsigned short *lut; ImageCharacteristics characteristics; /* Open image file. */ assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickSignature); assert(image != (Image *) NULL); assert(image->signature == MagickSignature); status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception); if (status == False) ThrowWriterException(FileOpenError,UnableToOpenFile,image); /* Ensure that image is in RGB space. */ (void) TransformColorspace(image,RGBColorspace); /* Analyze image to be written. */ if (!GetImageCharacteristics(image,&characteristics, (OptimizeType == image_info->type), &image->exception)) { CloseBlob(image); return MagickFail; } /* Obtain a JP2 stream. */ jp2_stream=JP2StreamManager(image); if (jp2_stream == (jas_stream_t *) NULL) ThrowWriterException(DelegateError,UnableToManageJP2Stream,image); number_components=image->matte ? 4 : 3; if ((image_info->type != TrueColorType) && (characteristics.grayscale)) number_components=1; jp2_image=jas_image_create0(); if (jp2_image == (jas_image_t *) NULL) ThrowWriterException(DelegateError,UnableToCreateImage,image); for (component=0; component < number_components; component++) { (void) memset((void *)&component_info,0,sizeof(jas_image_cmptparm_t)); component_info.tlx=0; /* top left x ordinate */ component_info.tly=0; /* top left y ordinate */ component_info.hstep=1; /* horizontal pixels per step */ component_info.vstep=1; /* vertical pixels per step */ component_info.width=(unsigned int) image->columns; component_info.height=(unsigned int) image->rows; component_info.prec=(unsigned int) Max(2,Min(image->depth,16)); /* bits in range */ component_info.sgnd = false; /* range is signed value? */ if (jas_image_addcmpt(jp2_image, component,&component_info)) { jas_image_destroy(jp2_image); ThrowWriterException(DelegateError,UnableToCreateImageComponent,image); } } /* Allocate and compute LUT. */ { unsigned long i, max_value; double scale_to_component; lut=MagickAllocateArray(unsigned short *,MaxMap+1,sizeof(*lut)); if (lut == (unsigned short *) NULL) { jas_image_destroy(jp2_image); ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image); } max_value=MaxValueGivenBits(component_info.prec); scale_to_component=max_value/MaxRGBDouble; for(i=0; i <= MaxMap; i++) lut[i]=scale_to_component*i+0.5; } if (number_components == 1) { /* FIXME: If image has an attached ICC profile, then the profile should be transferred and the image colorspace set to JAS_CLRSPC_GENGRAY */ /* sRGB Grayscale */ (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Setting SGRAY colorspace"); jas_image_setclrspc(jp2_image, JAS_CLRSPC_SGRAY); (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Setting GRAY channel to channel 0"); jas_image_setcmpttype(jp2_image,0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_GRAY_Y)); } else { /* FIXME: If image has an attached ICC profile, then the profile should be transferred and the image colorspace set to JAS_CLRSPC_GENRGB */ /* sRGB */ (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Setting SRGB colorspace"); jas_image_setclrspc(jp2_image, JAS_CLRSPC_SRGB); (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Setting RED channel to channel 0"); jas_image_setcmpttype(jp2_image,0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R)); (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Setting GREEN channel to channel 1"); jas_image_setcmpttype(jp2_image,1, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G)); (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Setting BLUE channel to channel 2"); jas_image_setcmpttype(jp2_image,2, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B)); if (number_components == 4 ) { (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Setting OPACITY channel to channel 3"); jas_image_setcmpttype(jp2_image,3, JAS_IMAGE_CT_COLOR(JAS_IMAGE_CT_OPACITY)); } } /* Convert to JPEG 2000 pixels. */ jp2_pixels=jas_matrix_create(1,(unsigned int) image->columns); if (jp2_pixels == (jas_matrix_t *) NULL) { MagickFreeMemory(lut); jas_image_destroy(jp2_image); ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image); } for (y=0; y < (long) image->rows; y++) { p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception); if (p == (const PixelPacket *) NULL) break; if (number_components == 1) { for (x=0; x < (long) image->columns; x++) jas_matrix_setv(jp2_pixels,x,lut[ScaleQuantumToMap(PixelIntensityToQuantum(&p[x]))]); (void) jas_image_writecmpt(jp2_image,0,0,(unsigned int) y, (unsigned int) image->columns,1,jp2_pixels); } else { for (x=0; x < (long) image->columns; x++) jas_matrix_setv(jp2_pixels,x,lut[ScaleQuantumToMap(p[x].red)]); (void) jas_image_writecmpt(jp2_image,0,0,(unsigned int) y, (unsigned int) image->columns,1,jp2_pixels); for (x=0; x < (long) image->columns; x++) jas_matrix_setv(jp2_pixels,x,lut[ScaleQuantumToMap(p[x].green)]); (void) jas_image_writecmpt(jp2_image,1,0,(unsigned int) y, (unsigned int) image->columns,1,jp2_pixels); for (x=0; x < (long) image->columns; x++) jas_matrix_setv(jp2_pixels,x,lut[ScaleQuantumToMap(p[x].blue)]); (void) jas_image_writecmpt(jp2_image,2,0,(unsigned int) y, (unsigned int) image->columns,1,jp2_pixels); if (number_components > 3) for (x=0; x < (long) image->columns; x++) jas_matrix_setv(jp2_pixels,x,lut[ScaleQuantumToMap(MaxRGB-p[x].opacity)]); (void) jas_image_writecmpt(jp2_image,3,0,(unsigned int) y, (unsigned int) image->columns,1,jp2_pixels); } if (image->previous == (Image *) NULL) if (QuantumTick(y,image->rows)) if (!MagickMonitorFormatted(y,image->rows,&image->exception, SaveImageText,image->filename, image->columns,image->rows)) break; } (void) strlcpy(magick,image_info->magick,MaxTextExtent); /* J2C is an alias for JPC but Jasper only supports "JPC". */ if (LocaleCompare(magick,"j2c") == 0) (void) strlcpy(magick,"jpc",sizeof(magick)); LocaleLower(magick); format=jas_image_strtofmt(magick); /* Support passing Jasper options. */ { const char **option_name; static const char *jasper_options[] = { "imgareatlx", "imgareatly", "tilegrdtlx", "tilegrdtly", "tilewidth", "tileheight", "prcwidth", "prcheight", "cblkwidth", "cblkheight", "mode", "ilyrrates", "prg", "nomct", "numrlvls", "sop", "eph", "lazy", "rate", "termall", "segsym", "vcausal", "pterm", "resetprob", "numgbits", NULL }; for (option_name = jasper_options; *option_name != NULL; option_name++) { const char *value; if ((value=AccessDefinition(image_info,"jp2",*option_name)) != NULL) { if(LocaleCompare(*option_name,"rate") == 0) rate_specified=True; FormatString(option_keyval,"%s=%.1024s ",*option_name,value); ConcatenateString(&options,option_keyval); } } } /* Provide an emulation of IJG JPEG "quality" by default. */ if (rate_specified == False) { double rate=1.0; /* A rough approximation to JPEG v1 quality using JPEG-2000. Default "quality" 75 results in a request for 16:1 compression, which results in image sizes approximating that of JPEG v1. */ if ((image_info->quality < 99.5) && (image->rows*image->columns > 2500)) { double header_size, current_size, target_size, d; d=115-image_info->quality; /* Best number is 110-115 */ rate=100.0/(d*d); header_size=550.0; /* Base file size. */ header_size+=(number_components-1)*142; /* Additional components */ /* FIXME: Need to account for any ICC profiles here */ current_size=(double)((image->rows*image->columns*image->depth)/8)* number_components; target_size=(current_size*rate)+header_size; rate=target_size/current_size; } FormatString(option_keyval,"%s=%g ","rate",rate); ConcatenateString(&options,option_keyval); (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Compression rate: %g (%3.2f:1)",rate,1.0/rate); } if (options) (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Jasper options: \"%s\"", options); (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Encoding image"); status=jas_image_encode(jp2_image,jp2_stream,format,options); (void) jas_stream_close(jp2_stream); MagickFreeMemory(options); MagickFreeMemory(lut); jas_matrix_destroy(jp2_pixels); jas_image_destroy(jp2_image); if (status) ThrowWriterException(DelegateError,UnableToEncodeImageFile,image); return(True); }
/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % W r i t e P C L I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Method WritePCLImage writes an image in the Page Control Language encoded % image format. % % The format of the WritePCLImage method is: % % unsigned int WritePCLImage(const ImageInfo *image_info,Image *image) % % A description of each parameter follows. % % o status: Method WritePCLImage return True if the image is written. % False is returned is there is a memory shortage or if the image file % fails to write. % % o image_info: Specifies a pointer to a ImageInfo structure. % % o image: A pointer to an Image structure. % % % */ static unsigned int WritePCLImage(const ImageInfo *image_info,Image *image) { char buffer[MaxTextExtent]; long sans, y; register const PixelPacket *p; register const IndexPacket *indexes; register long i, x; register unsigned char *q; unsigned char *pixels, *last_row_pixels, *output_row; unsigned int status; long zero_rows; unsigned long bytes_to_write, scene, density, bytes_per_line; unsigned char bits_per_pixel; ImageCharacteristics characteristics; PCL_CompressionType compression, last_row_compression; /* Open output image file. */ assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickSignature); assert(image != (Image *) NULL); assert(image->signature == MagickSignature); status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception); if (status == False) ThrowWriterException(FileOpenError,UnableToOpenFile,image); (void) GetGeometry("75x75",&sans,&sans,&density,&density); if (image_info->density != (char *) NULL) (void) GetGeometry(image_info->density,&sans,&sans,&density,&density); scene = 0; output_row = (unsigned char *) NULL; last_row_pixels = (unsigned char *) NULL; do { /* Ensure that image is in an RGB space. */ (void) TransformColorspace(image,RGBColorspace); /* Analyze image to be written. */ if (!GetImageCharacteristics(image,&characteristics, (OptimizeType == image_info->type), &image->exception)) { CloseBlob(image); return MagickFail; } /* Initialize the printer */ (void) WriteBlobString(image,"\033E"); /* printer reset */ (void) WriteBlobString(image,"\033*r3F"); /* set presentation mode */ /* define columns and rows in image */ FormatString(buffer,"\033*r%lus%luT",image->columns,image->rows); (void) WriteBlobString(image,buffer); FormatString(buffer,"\033*t%luR",density); /* set resolution */ (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"\033&l0E"); /* top margin 0 */ /* Determine output type and initialize further accordingly */ if (image->storage_class == DirectClass) { /* Full color */ bits_per_pixel=24; (void) WriteBlobString(image,"\033*v6W"); /* set color mode... */ (void) WriteBlobByte(image,0); /* RGB */ (void) WriteBlobByte(image,3); /* direct by pixel */ (void) WriteBlobByte(image,0); /* bits per index (ignored) */ (void) WriteBlobByte(image,8); /* bits per red component */ (void) WriteBlobByte(image,8); /* bits per green component */ (void) WriteBlobByte(image,8); /* bits per blue component */ } else if (characteristics.monochrome) { /* Use default printer monochrome setup - NB white = 0, black = 1 */ bits_per_pixel=1; } else { /* PseudoClass */ bits_per_pixel=8; (void) WriteBlobString(image,"\033*v6W"); /* set color mode... */ (void) WriteBlobByte(image,0); /* RGB */ (void) WriteBlobByte(image,1); /* indexed by pixel */ (void) WriteBlobByte(image,bits_per_pixel); /* bits per index */ (void) WriteBlobByte(image,8); /* bits per red component (implicit) */ (void) WriteBlobByte(image,8); /* bits per green component (implicit) */ (void) WriteBlobByte(image,8); /* bits per blue component (implicit) */ /* Write colormap to file. */ for (i=0; i < (long)(image->colors); i++) { FormatString(buffer,"\033*v%da%db%dc%ldI", ScaleQuantumToChar(image->colormap[i].red), ScaleQuantumToChar(image->colormap[i].green), ScaleQuantumToChar(image->colormap[i].blue), i); WriteBlobString(image,buffer); } /* Initialize rest of palette with empty entries */ for ( ; i < (1L << bits_per_pixel); i++) { FormatString(buffer,"\033*v%luI",i); /* set index to current component values */ (void) WriteBlobString(image,buffer); } } /* Start raster image */ if ((AccessDefinition(image_info,"pcl","fit-to-page") != NULL) || (AccessDefinition(image_info,"pcl","fit_to_page") != NULL)) (void) WriteBlobString(image,"\033*r3A"); /* start raster graphics with scaling */ else (void) WriteBlobString(image,"\033*r1A"); /* start raster graphics */ (void) WriteBlobString(image,"\033*b0Y"); /* set y offset */ /* Assign row buffer */ bytes_per_line=(image->columns*bits_per_pixel+7)/8; pixels=MagickAllocateMemory(unsigned char *,bytes_per_line); if (pixels == (unsigned char *) NULL) ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image); /* Set up for compression if desired */ last_row_compression = PCL_UndefinedCompression; if (image_info->compression != NoCompression) { MagickFreeMemory(last_row_pixels); last_row_pixels=MagickAllocateMemory(unsigned char *,bytes_per_line); if (last_row_pixels == (unsigned char *) NULL) { MagickFreeMemory(pixels); ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image); } MagickFreeMemory(output_row); output_row=MagickAllocateMemory(unsigned char *,bytes_per_line); if (output_row == (unsigned char *) NULL) { MagickFreeMemory(pixels); MagickFreeMemory(last_row_pixels); ThrowWriterException(ResourceLimitError,MemoryAllocationFailed,image); } memset(last_row_pixels,0,bytes_per_line); } /* Convert MIFF to PCL raster pixels. */ zero_rows=0; for (y=0; y < (long) image->rows; y++) { p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception); if (p == (const PixelPacket *) NULL) break; q=pixels; if (characteristics.monochrome) { register unsigned char bit, byte; int blk_ind; /* Monochrome row */ blk_ind = ((image->colormap == NULL) || (image->colormap[0].red == 0)) ? 0 : 1; indexes=AccessImmutableIndexes(image); bit=0; byte=0; for (x=0; x < (long) image->columns; x++) { byte<<=1; if (indexes[x] == blk_ind) byte |= 1; bit++; if (bit == 8) { *q++=byte; bit=0; byte=0; } } if (bit != 0) *q++=byte << (8-bit); } else if (bits_per_pixel == 8) { /* 8 bit PseudoClass row */ indexes=AccessImmutableIndexes(image); for (x=0; x < (long) image->columns; x++) { *q++=indexes[x]; } } else if ((bits_per_pixel == 24) || (bits_per_pixel == 32)) { /* DirectClass/RGB row */ for (x=0; x < (long) image->columns; x++) { *q++=ScaleQuantumToChar(p->red); *q++=ScaleQuantumToChar(p->green); *q++=ScaleQuantumToChar(p->blue); p++; } } if (image_info->compression == NoCompression) { FormatString(buffer,"\033*b%luW",bytes_per_line); /* send row */ (void) WriteBlobString(image,buffer); (void) WriteBlob(image,bytes_per_line,pixels); } else { compression=PCL_ChooseCompression(bytes_per_line,pixels,last_row_pixels); if (compression == PCL_ZeroRowCompression) { zero_rows++; } else { /* Skip any omitted zero rows now */ if (zero_rows > 0) { i = 32767; do { if (zero_rows < i) i=zero_rows; FormatString(buffer,"\033*b%ldY",i); /* Y Offset */ (void) WriteBlobString(image,buffer); zero_rows -= i; } while (zero_rows > 0); } switch (compression) { case PCL_DeltaCompression: { if (compression != last_row_compression) { FormatString(buffer,"\033*b3M"); /* delta compression */ (void) WriteBlobString(image,buffer); last_row_compression=compression; } bytes_to_write=PCL_DeltaCompress(bytes_per_line,pixels, last_row_pixels,output_row); FormatString(buffer,"\033*b%luW",bytes_to_write); (void) WriteBlobString(image,buffer); WriteBlob(image,bytes_to_write,output_row); break; } case PCL_TiffRLECompression: { if (compression != last_row_compression) { FormatString(buffer,"\033*b2M"); /* Tiff RLE compression */ (void) WriteBlobString(image,buffer); last_row_compression=compression; } bytes_to_write=PCL_TiffRLECompress(bytes_per_line,pixels,output_row); FormatString(buffer,"\033*b%luW",bytes_to_write); (void) WriteBlobString(image,buffer); WriteBlob(image,bytes_to_write,output_row); break; } case PCL_RLECompression: { if (compression != last_row_compression) { FormatString(buffer,"\033*b1M"); /* RLE compression */ (void) WriteBlobString(image,buffer); last_row_compression=compression; } bytes_to_write=PCL_RLECompress(bytes_per_line,pixels,output_row); FormatString(buffer,"\033*b%luW",bytes_to_write); (void) WriteBlobString(image,buffer); WriteBlob(image,bytes_to_write,output_row); break; } case PCL_RepeatedRowCompression: { compression=PCL_DeltaCompression; if (compression != last_row_compression) { FormatString(buffer,"\033*b3M"); /* delta row compression */ (void) WriteBlobString(image,buffer); last_row_compression=compression; } FormatString(buffer,"\033*b0W"); /* no data -> replicate row */ (void) WriteBlobString(image,buffer); break; } case PCL_NoCompression: { if (compression != last_row_compression) { FormatString(buffer,"\033*b0M"); /* no compression */ (void) WriteBlobString(image,buffer); last_row_compression=compression; } FormatString(buffer,"\033*b%luW",bytes_per_line); /* send row */ (void) WriteBlobString(image,buffer); (void) WriteBlob(image,bytes_per_line,pixels); break; } case PCL_ZeroRowCompression: { break; } case PCL_UndefinedCompression: { break; } } } /* Swap row with last row */ q=last_row_pixels; last_row_pixels=pixels; pixels=q; } if (image->previous == (Image *) NULL) if (QuantumTick(y,image->rows)) if (!MagickMonitorFormatted(y,image->rows,&image->exception, SaveImageText,image->filename, image->columns,image->rows)) break; } (void) WriteBlobString(image,"\033*rB"); /* end graphics */ MagickFreeMemory(pixels); MagickFreeMemory(last_row_pixels); MagickFreeMemory(output_row); if (image->next == (Image *) NULL) break; image=SyncNextImageInList(image); if ((status &= MagickMonitorFormatted(scene++, GetImageListLength(image), &image->exception, SaveImagesText, image->filename)) == MagickFail) break; } while (image_info->adjoin);