def test_profile_typesafety(self):
        """ Profile init type safety

        prepatch, these would segfault, postpatch they should emit a typeerror
        """

        with self.assertRaises(TypeError):
            ImageCms.ImageCmsProfile(0).tobytes()
        with self.assertRaises(TypeError):
            ImageCms.ImageCmsProfile(1).tobytes()
예제 #2
0
 def make_prw(self, prw_name, image=None, LayoutQC_DEBAG=False):
     if not image:
         image = self.image
     if not self.image.info.get('icc_profile', ''):
         profile_assighned = ImageCms.ImageCmsProfile(io.BytesIO(euroscale_profile.tobytes()))
         if LayoutQC_DEBAG:
             print("Untagged layout, assighned profile Euroscale Coated for converting to RGB preview")
     else:
         profile_assighned = ImageCms.ImageCmsProfile(io.BytesIO(self.image.info.get('icc_profile', '')))
     screen = ImageCms.profileToProfile(image, profile_assighned, screen_profile, 0, 'RGB')
     screen.save(prw_name, quality=100, dpi=(self.resolution, self.resolution), )
예제 #3
0
def _decode_icc(data):
    try:
        from PIL import ImageCms
    except ImportError:
        return data

    return ImageCms.ImageCmsProfile(io.BytesIO(data))
예제 #4
0
def read_and_make(item):
    print('reading %s...' %item)
    item_path = os.path.join(target_path,item)
    srcs = []
    if not os.path.isdir(item_path):
        os.makedirs(item_path)
    for file in os.listdir(item_path):
        if file.split('.')[-1].upper() in ['JPG', 'JPEG', 'PNG']:
            img = Image.open(os.path.join(item_path,file))
            icc_profile = img.info.get('icc_profile')
            f = io.BytesIO(icc_profile)
            try:
                icc=ImageCms.ImageCmsProfile(f)
                img = ImageCms.profileToProfile(img,icc,srgb)
            except:
                pass
            srcs.append(np.asarray(img))
    if srcs!=[]:
        try:
            if not os.path.isdir(gif_path):
                os.makedirs(gif_path)
            print('saving %s.gif' %item)
            imageio.mimwrite(os.path.join(gif_path,item+'.gif'), srcs, duration=1)
        except:
            print('ERROR: An error occured! Target images might have different sizes or this is an IO error.')
예제 #5
0
def _apply_icc(psd, image):
    """Apply ICC Color profile."""
    from io import BytesIO
    try:
        from PIL import ImageCms
    except ImportError:
        logger.debug(
            'ICC profile found but not supported. Install little-cms.'
        )
        return image

    if image.mode not in ('RGB',):
        logger.debug('%s ICC profile is not supported.' % image.mode)
        return image

    try:
        in_profile = ImageCms.ImageCmsProfile(
            BytesIO(psd.image_resources.get_data('ICC_PROFILE'))
        )
        out_profile = ImageCms.createProfile('sRGB')
        return ImageCms.profileToProfile(image, in_profile, out_profile)
    except ImageCms.PyCMSError as e:
        logger.warning('PyCMSError: %s' % (e))

    return image
예제 #6
0
 def imageRGB(self, prof_out=False):
     image = self.image()
     alpha_band = False
     if image.mode in ('LA', 'PA', 'RGBA'):
         bands = image.split()
         alpha_band = bands[-1]
         image = Image.merge(image.mode[:-1], bands[:-1])
     sRGB = ImageCms.createProfile("sRGB")
     if 'icc_profile' in image.info:
         inputProfile = ImageCms.ImageCmsProfile(
             StringIO(image.info['icc_profile']))
     else:
         if image.mode == "CMYK":
             inputProfile = (dirpath(__file__) or ".") + "/Fogra27L.icm"
         elif image.mode == "LAB":
             inputProfile = ImageCms.createProfile("LAB")
         else:
             image = image.convert('RGB')
             inputProfile = sRGB
     if prof_out:
         outputProfile = prof_out
     else:
         outputProfile = sRGB
     new_image = ImageCms.profileToProfile(image,
                                           inputProfile,
                                           outputProfile,
                                           outputMode="RGB")
     if not alpha_band:
         alpha_band = Image.new('L', image.size, 255)
     new_image.putalpha(alpha_band)
     return new_image
예제 #7
0
def convert_to_srgb(img):
    icc = img.info.get('icc_profile', '')
    if icc:
        fd = io.BytesIO(icc)
        sPrf = ImageCms.ImageCmsProfile(fd)
        dPrf = ImageCms.createProfile('sRGB')
        img = ImageCms.profileToProfile(img, sPrf, dPrf)
    return img
예제 #8
0
def get_image_np(image_name):
    image_path = 'images/' + image_name
    image = Image.open(image_path)

    iccProfile = image.info.get('icc_profile')
    iccBytes = io.BytesIO(iccProfile)
    originalColorProfile = ImageCms.ImageCmsProfile(iccBytes)

    return (np.array(image.convert('RGB')), originalColorProfile)
예제 #9
0
def convert_to_srgb(img):
    '''Convert PIL image to sRGB color space (if possible)'''
    icc = img.info.get('icc_profile', '')
    if icc:
        io_handle = io.BytesIO(icc)  # virtual file
        src_profile = ImageCms.ImageCmsProfile(io_handle)
        dst_profile = ImageCms.createProfile('sRGB')
        img = ImageCms.profileToProfile(img, src_profile, dst_profile)
    return img
예제 #10
0
def _decode_icc(data):
    try:
        from PIL import ImageCms
        return ImageCms.ImageCmsProfile(io.BytesIO(data))
    except ImportError:
        warnings.warn(
            "ICC profile is not handled; colors could be incorrect. "
            "Please build PIL or Pillow with littlecms/littlecms2 support.")
        return data
예제 #11
0
 def convert_to_srgb(self, img):
     """Convert PIL image to sRGB color space (if possible)"""
     icc = img.info.get("icc_profile", "")
     if icc:
         io_handle = io.BytesIO(icc)  # virtual file
         src_profile = ImageCms.ImageCmsProfile(io_handle)
         dst_profile = ImageCms.createProfile("sRGB")
         img = ImageCms.profileToProfile(img, src_profile, dst_profile)
     return img
예제 #12
0
    def test_icc_conversion(self):
        with temporary_folder() as output_folder:
            output_file = os.path.join(output_folder, 'output.tif')
            conversion.Converter().convert_icc_profile(
                filepaths.STANDARD_TIF, output_file,
                filepaths.SRGB_ICC_PROFILE)
            assert os.path.isfile(output_file)

            with Image.open(filepaths.STANDARD_TIF) as input_pil:
                old_icc = input_pil.info.get('icc_profile')
                f = io.BytesIO(old_icc)
                prf = ImageCms.ImageCmsProfile(f)
                assert prf.profile.profile_description == "Adobe RGB (1998)"

            with Image.open(output_file) as output_pil:
                new_icc = output_pil.info.get('icc_profile')
                f = io.BytesIO(new_icc)
                prf = ImageCms.ImageCmsProfile(f)
                assert prf.profile.profile_description == "sRGB v4 ICC preference perceptual intent beta"
예제 #13
0
def NormaliseICCProfilePILImageToSRGB(
        pil_image: PILImage.Image) -> PILImage.Image:

    try:

        icc_profile_bytes = GetICCProfileBytes(pil_image)

    except HydrusExceptions.DataMissing:

        return pil_image

    try:

        f = io.BytesIO(icc_profile_bytes)

        src_profile = PILImageCms.ImageCmsProfile(f)

        if pil_image.mode in ('L', 'LA'):

            # had a bunch of LA pngs that turned pure white on RGBA ICC conversion
            # but seem to work fine if keep colourspace the same for now
            # it is a mystery, I guess a PIL bug, but presumably L and LA are technically sRGB so it is still ok to this

            outputMode = pil_image.mode

        else:

            if PILImageHasAlpha(pil_image):

                outputMode = 'RGBA'

            else:

                outputMode = 'RGB'

        pil_image = PILImageCms.profileToProfile(pil_image,
                                                 src_profile,
                                                 PIL_SRGB_PROFILE,
                                                 outputMode=outputMode)

    except (PILImageCms.PyCMSError, OSError):

        # 'cannot build transform' and presumably some other fun errors
        # way more advanced than we can deal with, so we'll just no-op

        # OSError is due to a "OSError: cannot open profile from string" a user got
        # no idea, but that seems to be an ImageCms issue doing byte handling and ending up with an odd OSError?
        # or maybe somehow my PIL reader or bytesIO sending string for some reason?
        # in any case, nuke it for now

        pass

    pil_image = NormalisePILImageToRGB(pil_image)

    return pil_image
예제 #14
0
def thumbnailer(fileSkel, existingFiles, params):
    blob = bucket.get_blob("%s/source/%s" %
                           (fileSkel["dlkey"], fileSkel["name"]))
    if not blob:
        logging.warning("Blob %s is missing from Cloudstore!" %
                        fileSkel["dlkey"])
        return
    fileData = BytesIO()
    blob.download_to_file(fileData)
    resList = []
    for sizeDict in params:
        fileData.seek(0)
        outData = BytesIO()
        try:
            img = Image.open(fileData)
        except Image.UnidentifiedImageError:  # We can't load this image; so there's no need to try other resolutions
            return []
        iccProfile = img.info.get('icc_profile')
        if iccProfile:
            # JPEGs might be encoded with a non-standard color-profile; we need to compensate for this if we convert
            # to WEBp as we'll loose this color-profile information
            f = BytesIO(iccProfile)
            src_profile = ImageCms.ImageCmsProfile(f)
            dst_profile = ImageCms.createProfile('sRGB')
            img = ImageCms.profileToProfile(img,
                                            inputProfile=src_profile,
                                            outputProfile=dst_profile,
                                            outputMode="RGB")
        fileExtension = sizeDict.get("fileExtension", "webp")
        if "width" in sizeDict and "height" in sizeDict:
            width = sizeDict["width"]
            height = sizeDict["height"]
            targetName = "thumbnail-%s-%s.%s" % (width, height, fileExtension)
        elif "width" in sizeDict:
            width = sizeDict["width"]
            height = int(
                (float(img.size[1]) * float(width / float(img.size[0]))))
            targetName = "thumbnail-w%s.%s" % (width, fileExtension)
        else:  # No default fallback - ignore
            continue
        mimeType = sizeDict.get("mimeType", "image/webp")
        img = img.resize((width, height), Image.ANTIALIAS)
        img.save(outData, fileExtension)
        outSize = outData.tell()
        outData.seek(0)
        targetBlob = bucket.blob("%s/derived/%s" %
                                 (fileSkel["dlkey"], targetName))
        targetBlob.upload_from_file(outData, content_type=mimeType)
        resList.append((targetName, outSize, mimeType, {
            "mimetype": mimeType,
            "width": width,
            "height": height
        }))
    return resList
예제 #15
0
파일: imgutil.py 프로젝트: GeorgeCh2/myimg
def convert_to_srgb(img):
    """
    convert to sRGB if image contain 'icc_profile'
    :param img:
    :return:
    """
    # get icc profile from img info
    icc = img.info.get('icc_profile', '')
    if icc:
        # convert to sRGB
        io_handle = io.BytesIO(icc)
        src_profile = ImageCms.ImageCmsProfile(io_handle)
        dst_profile = ImageCms.createProfile('sRGB')
        img = ImageCms.profileToProfile(img, src_profile, dst_profile)
    return img
예제 #16
0
 def screencap(self):
     from PIL import Image as PILImage, ImageCms
     s = self.device_session_factory().exec_stream(self.command)
     data = recvall(s, 4194304, True)
     img = PILImage.open(BytesIO(data))
     if self.screenshot_rotate != 0:
         img = img.rotate(self.screenshot_rotate)
     if icc := img.info.get('icc_profile', ''):
         iccio = BytesIO(icc)
         src_profile = ImageCms.ImageCmsProfile(iccio)
         from imgreco.cms import srgb_profile
         ImageCms.profileToProfile(img,
                                   src_profile,
                                   srgb_profile,
                                   ImageCms.INTENT_RELATIVE_COLORIMETRIC,
                                   inPlace=True)
예제 #17
0
    def icc(self):
        """If an ICC profile is attached, return a Pillow object that describe it.

        Most of the information may be found in ``icc.profile``.

        Returns:
            PIL.ImageCms.ImageCmsProfile
        """
        if self.colorspace not in ('/ICCBased', '/Indexed'):
            return None
        if not self._icc:
            iccstream = self._iccstream
            iccbuffer = iccstream.get_stream_buffer()
            iccbytesio = BytesIO(iccbuffer)
            self._icc = ImageCms.ImageCmsProfile(iccbytesio)
        return self._icc
예제 #18
0
def test_icc_palette(resources):
    xobj, _pdf = first_image_in(resources / 'pink-palette-icc.pdf')
    pim = PdfImage(xobj)
    assert pim.icc.profile.xcolor_space == 'RGB '  # with trailing space
    b = BytesIO()
    pim.extract_to(stream=b)
    b.seek(0)

    im = Image.open(b)
    assert im.size == (xobj.Width, xobj.Height)
    assert im.mode == 'P'
    pil_icc = im.info.get('icc_profile')
    pil_icc_stream = BytesIO(pil_icc)
    pil_prf = ImageCms.ImageCmsProfile(pil_icc_stream)

    assert pil_prf.tobytes() == pim.icc.tobytes()
예제 #19
0
def convert_to_srgb(file_path):
    '''Convert PIL image to sRGB color space (if possible)'''
    img = Image.open(file_path)
    icc = img.info.get('icc_profile', '')

    if icc:
        io_handle = io.BytesIO(icc)  # virtual file
        src_profile = ImageCms.ImageCmsProfile(io_handle)
        dst_profile = ImageCms.createProfile('sRGB')
        img_conv = ImageCms.profileToProfile(img, src_profile, dst_profile)
        icc_conv = img_conv.info.get('icc_profile', '')

        if icc != icc_conv:
            # ICC profile was changed -> save converted file
            img_conv.save(file_path,
                          format='JPEG',
                          quality=50,
                          icc_profile=icc_conv)
예제 #20
0
def convert_to_srgb(img):
    '''Convert PIL image to sRGB color space (if possible)'''
    # ok there might be a problem using this function
    # to solve it, completely remove PIL and Pillow package
    # then reinstall it using pip or conda or whatever
    # this is a pretty shitty problem
    # and there is no other solution other than this
    icc = img.info.get('icc_profile', '')
    if_changed = False
    if icc:
        try:
            io_handle = io.BytesIO(icc)  # virtual file
            src_profile = ImageCms.ImageCmsProfile(io_handle)
            dst_profile = ImageCms.createProfile('sRGB')
            img = ImageCms.profileToProfile(img, src_profile, dst_profile)
            if_changed = True
        except:
            ## this happens when we cannot handle the color profile, f**k this
            pass
    return img, if_changed
예제 #21
0
    def icc(self) -> Optional[ImageCms.ImageCmsProfile]:
        """If an ICC profile is attached, return a Pillow object that describe it.

        Most of the information may be found in ``icc.profile``.

        """
        if self.colorspace not in ('/ICCBased', '/Indexed'):
            return None
        if not self._icc:
            iccstream = self._iccstream
            iccbuffer = iccstream.get_stream_buffer()
            iccbytesio = BytesIO(iccbuffer)
            try:
                self._icc = ImageCms.ImageCmsProfile(iccbytesio)
            except OSError as e:
                if str(e) == 'cannot open profile from string':
                    # ICC profile is corrupt
                    raise UnsupportedImageTypeError(
                        "ICC profile corrupt or not readable"
                    ) from e
        return self._icc
예제 #22
0
from . import resources
from PIL import ImageCms
p3_profile = ImageCms.ImageCmsProfile(resources.open_file('DisplayP3.icm'))
srgb_profile = ImageCms.createProfile('sRGB')

# numpy is slower than ImageCms (LittleCMS)

# import numpy as np

# denorm8 = np.arange(256, dtype=np.float32) / 255
# denorm_degamma8 = np.piecewise(denorm8, [denorm8 <= 0.04045, denorm8 > 0.04045], [lambda x: x / 12.92, lambda x: ((x + 0.055) / 1.055) ** 2.4])
# norm10 = np.arange(1024, dtype=np.float32) / 1023
# regamma10 = np.piecewise(norm10, [norm10 <= 0.0031308, norm10 > 0.0031308], [lambda x: x * 12.92, lambda x: 1.055 * (x ** (1.0 / 2.4)) - 0.055])
# regamma10_norm8 = np.round(regamma10 * 255).astype(np.uint8)

# def matrix_for_primaries(red, green, blue, white):
#     Yxy = np.array([(1, *red), (1, *green), (1, *blue), (1, *white)], dtype=np.float32)
#     x = Yxy[:, 1]
#     y = Yxy[:, 2]
#     X = x / y
#     Z = (1 - x - y) / y
#     XYZ = np.array([X, Yxy[:, 0], Z])
#     S = np.linalg.inv(XYZ[..., :3]) @ XYZ[..., 3]
#     M = S * XYZ[..., :3]
#     return M

# M_srgb_to_xyz = matrix_for_primaries((0.64, 0.33), (0.3, 0.6), (0.15, 0.06), (0.3127, 0.3290))
# M_xyz_to_srgb = np.linalg.inv(M_srgb_to_xyz)
# M_p3_to_xyz = matrix_for_primaries((0.680, 0.320), (0.265, 0.690), (0.150, 0.060), (0.3127, 0.3290))
# M_p3_to_srgb = M_xyz_to_srgb @ M_p3_to_xyz
예제 #23
0
	def profile_conversion(image):
		maybe_icc = image.info.get("icc_profile")
		if not prefer_embedded or maybe_icc is None:
			return ImageCms.applyTransform(image, transform)
		em_profile = ImageCms.ImageCmsProfile(io.BytesIO(maybe_icc))
		return ImageCms.profileToProfile(image, em_profile, profiles[1], renderingIntent=intent, outputMode=modes[1])
예제 #24
0
 def ConvertMode_and_Icc(self, prof=euroscale_profile):
     conv_mode_profile = ImageCms.ImageCmsProfile(io.BytesIO(prof.tobytes()))
     im_mode_profile = ImageCms.ImageCmsProfile(io.BytesIO(self.image.info.get('icc_profile', '')))
     self.image = ImageCms.profileToProfile(self.image, im_mode_profile, conv_mode_profile, 0, 'CMYK')
     self.prw_name = 'temp/convert_ICC_temp.png'
     self.make_prw(self.prw_name)
예제 #25
0
def resize_img(path, size):

    files_exten = [
        'jpg',
        'JPG',
        'JPEG',
        'png',
        'tiff',
        'tif',
        'TIFF',
        'PNG',
    ]

    print(f'Path to search: {path}')

    files = []
    for file in os.listdir(path):
        x = file.split('.')
        if len(x) > 1:
            if x[1] in files_exten:
                files.append(file)

    extentions = []

    for ext in files:
        if '.' in ext:
            extention = ext.split('.')
            extentions.append(extention[1])

    print(f'{len(extentions)} find!')

    if os.path.isdir(path + '/resized_to_' + str(size) + 'px'):
        pass
    else:
        os.mkdir(path + '/resized_to_' + str(size) + 'px')

    count = 0
    for x in files:

        if extentions[count] in files_exten:
            dir_img = path + '/' + files[count]
            img = Image.open(dir_img)

            if img.mode == 'RGBA':
                img = img.convert('RGB')

            icc = img.info.get('icc_profile', '')

            if icc:
                handler = io.BytesIO(icc)
                src_profile = ImageCms.ImageCmsProfile(handler)
                dst_profile = ImageCms.createProfile('sRGB')

                img = ImageCms.profileToProfile(img, src_profile, dst_profile)

            new_size_y = int(size) / (int(img.size[0]) / int(img.size[1]))
            rezized = img.resize((size, int(new_size_y)), Image.LANCZOS)
            new_name = files[count].split('.')
            save_file = path + '/resized_to_' + str(size) + 'px/' + str(
                new_name[0]) + '.jpeg'
            rezized.save(save_file, 'JPEG', quality=90)
            print(f'image {files[count]} resized!')
            count += 1

        else:
            print(f'{x} not rezized')
            count += 1

    return count
예제 #26
0
    GAUSSIAN_BLUR_RADIUS = int(sys.argv[2])
elif len(sys.argv) == 2:
    IMAGE_FILE = sys.argv[1]
    GAUSSIAN_BLUR_RADIUS = 10
else:
    print("Not enough arguments provided. Exiting.")
    sys.exit()

# Open two versions of the image - one for the actual image to show and one to use as background
image = Image.open(IMAGE_FILE)
imageBackground = Image.open(IMAGE_FILE)

# Get original color profile so it can be preserved
iccProfile = image.info.get('icc_profile')
iccBytes = io.BytesIO(iccProfile)
originalColorProfile = ImageCms.ImageCmsProfile(iccBytes)

# some math
width, height = image.size
if width > height:
    ratio = width / height
else:
    ratio = height / width
incRatioSize = int(round(INSTAGRAM_PHOTO_SIZE * ratio))
decRatioSize = int(round(INSTAGRAM_PHOTO_SIZE / ratio))
x = int(round((incRatioSize - INSTAGRAM_PHOTO_SIZE) / 2))
y = int(round((INSTAGRAM_PHOTO_SIZE - decRatioSize) / 2))

# Prepare image and background sizes - background fills 2048 square, image is centered on it
if width > height:
    # Image background goes to height of instagram square
예제 #27
0
파일: handlePDF.py 프로젝트: cadnant/oomap
def createImage(path, fileformat):
    import tempfile
    import cairo
    try:  #DPD - get unquote regardless of Python version
        from urllib.parse import unquote  #Python3
    except ImportError:
        from urlparse import unquote  #Python2

    import overpass  #For real-time data query
    import time
    import psycopg2

    p = parse_query(path)

    EPSG900913 = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs +over"
    C_SCALE_FACTOR = 1.4
    SCALE_FACTOR = p['dpi'] / 72.0
    S2P = SCALE_FACTOR * 360 / 0.127

    # Feature widths/diameters and stroke thicknesses, in m. From the ISOM/ISSOM specs.
    SC_W = 0.007 * C_SCALE_FACTOR
    SC_T = 0.00035 * C_SCALE_FACTOR
    C_R = 0.003 * C_SCALE_FACTOR
    CDOT_R = 0.00035 * C_SCALE_FACTOR
    C_T = 0.00035 * C_SCALE_FACTOR
    CL_H = 0.005 * C_SCALE_FACTOR
    CTEXT_S = 0.005 * C_SCALE_FACTOR

    CONTENT_NM = 0.0045
    MAP_NM = 0.014
    MAP_EM = 0.008
    MAP_SM = 0.013
    MAP_WM = 0.008

    ADORN_TITLE_SM = 0.002
    ADORN_SCALE_SM = 0.005
    ADORN_SCALEBAR_SM = 0.002
    ADORN_SCALEBAR_LARGETICK = 0.002
    ADORN_SCALEBAR_SMALLTICK = 0.001
    ADORN_SCALEBAR_PADDING = 0.002
    ADORN_ATTRIB_NM = 0.0035
    ADORN_URL_NM = 0.001

    ADORN_LOGO_SCALE = 0.175 * SCALE_FACTOR  #0.038
    ADORN_LOGO_SCALE_IOA = 0.175 * SCALE_FACTOR  #= 0.25
    ADORN_ARROW_W = 0.012
    ADORN_LOGO_W = 0.018

    style = p['style']
    if style != "crew" and style != 'blueprint' and style != "urban_skeleton" and style != "streeto" and style != "oterrain" and style != "adhoc":
        return "Unknown style."

    PAPER_W = float(p['paper'].split(",")[0])
    PAPER_H = float(p['paper'].split(",")[1])
    scale = int(p['scale'])
    clat = int(p['centre'].split(",")[0])
    clon = int(p['centre'].split(",")[1])
    try:
        rotation = float(p['rotation'])
    except:
        rotation = 0
    try:
        title = p['title']
    except:
        title = "OpenOrienteeringMap"

    mapid = p.get('mapid', 'new')

    slon = slat = flon = flat = 0
    if 'start' in p:
        slat = flat = int(p['start'].split(",")[0])
        slon = flon = int(p['start'].split(",")[1])

    if 'finish' in p:
        flat = int(p['finish'].split(",")[0])
        flon = int(p['finish'].split(",")[1])

    controlsArr = []
    if 'controls' in p:
        controlsArr = p['controls'].split(",")

    crossesArr = []
    if 'crosses' in p:
        crossesArr = p['crosses'].split(",")

    cpsArr = []
    if 'cps' in p:
        cpsArr = p['cps'].split(",")

    projection = mapnik.Projection(EPSG900913)
    wgs84lat = mapnik.Coord(clon, clat).inverse(projection).y
    wgs84lon = mapnik.Coord(clon, clat).inverse(projection).x
    scaleCorrectionFactor = math.cos(wgs84lat * math.pi / 180)
    scaleCorrected = scale / scaleCorrectionFactor

    #get declination from local Python web service (declination.py; mod_wsgi alias for /wmm)
    wmmParams = {'lat': str(wgs84lat), 'lon': str(wgs84lon)}
    wmmResponse = requests.get(web_root + "wmm", params=wmmParams)
    magdec = float(wmmResponse.text)

    if style == "adhoc":
        MAP_EM = MAP_WM
        MAP_NM = MAP_WM
        MAP_SM = MAP_WM

    MAP_W = PAPER_W - MAP_WM - MAP_EM
    MAP_H = PAPER_H - MAP_NM - MAP_SM
    EXTENT_W = MAP_W * math.cos(rotation) + MAP_H * abs(math.sin(rotation))
    EXTENT_H = MAP_H * math.cos(rotation) + MAP_W * abs(math.sin(rotation))

    mapSLat = clat - (EXTENT_H / 2) * scaleCorrected
    mapNLat = clat + (EXTENT_H / 2) * scaleCorrected
    mapWLon = clon - (EXTENT_W / 2) * scaleCorrected
    mapELon = clon + (EXTENT_W / 2) * scaleCorrected

    pagePolyUnrotated = ((clon - (MAP_W / 2 + MAP_WM) * scaleCorrected,
                          clat - (MAP_H / 2 + MAP_SM) * scaleCorrected),
                         (clon - (MAP_W / 2 + MAP_WM) * scaleCorrected,
                          clat + (MAP_H / 2 + MAP_NM) * scaleCorrected),
                         (clon + (MAP_W / 2 + MAP_EM) * scaleCorrected,
                          clat + (MAP_H / 2 + MAP_NM) * scaleCorrected),
                         (clon + (MAP_W / 2 + MAP_EM) * scaleCorrected,
                          clat - (MAP_H / 2 + MAP_SM) * scaleCorrected))
    pagePoly = (rotate((clon, clat), pagePolyUnrotated[0], rotation),
                rotate((clon, clat), pagePolyUnrotated[1], rotation),
                rotate((clon, clat), pagePolyUnrotated[2], rotation),
                rotate((clon, clat), pagePolyUnrotated[3], rotation))

    styleFile = home + "/styles/" + style + ".xml"
    with open(styleFile, mode="r") as f:
        styleString = f.read()

    bbox2 = mapnik.Box2d(mapWLon, mapSLat, mapELon,
                         mapNLat).inverse(projection)

    if len(mapid
           ) == 13:  #If existing id, use that, otherwise generate new one.
        tmpid = "h" + mapid  #Add "h" prefix - postgres tables can't start with a number
    else:
        tmpid = "h" + hex(int(time.time()))[2:10] + hex(
            int(time.time() * 1000000) % 0x100000)[2:7]
    styleFile = home + "/styles/" + tmpid + ".xml"

    # Get contour attribution from custom Postgres DB table
    # based on centre location and type (e.g. "LIDAR") of contour selected.
    conn = psycopg2.connect(database="gis",
                            user="******",
                            host="127.0.0.1",
                            port="5432")
    cur = conn.cursor()
    cur.execute(
        """
        select attrib, tablename from attrib WHERE
            ST_Within(ST_SetSRID(ST_Point(%s, %s),900913),way)
            and type = %s;
        """, (clon, clat, p['contour']))
    # If at least 1 hit choose the first one, otherwise no contours available.
    if cur.rowcount > 0:
        db_result = cur.fetchone()
        contour_text = "Contours: " + db_result[0]
        contour_table = db_result[1]
    else:
        contour_text = ""
        contour_table = "lidar_null"

    # If stylefile exists, data has been recently fetched - can use existing DB tables.
    # Recreate stylefile regardless - might need a new style on existing data.

    if not os.path.isfile(styleFile):
        # api = overpass.API()
        api = overpass.API(
            endpoint="https://overpass.kumi.systems/api/interpreter",
            timeout=120)
        MapQuery = overpass.MapQuery(bbox2.miny, bbox2.minx, bbox2.maxy,
                                     bbox2.maxx)
        try:
            response = api.get(MapQuery, responseformat="xml")
        except Exception as e:
            return "Overpass API error: " + type(e).__name__ + ", " + str(e) + "\n" + \
                "Use the following ID to recover your map: " + tmpid[1:]

        tmpname = "/tmp/" + tmpid + ".osm"
        with open(tmpname, mode="wb") as f:
            f.write(response.encode("utf-8"))
        # Populate Postgres db with data, using tables with temporary id prefix

        os.system("osm2pgsql -d otf1 --hstore --multi-geometry --number-processes 1" + \
            " -p " + tmpid + \
            " --tag-transform-script /home/osm/openstreetmap-carto/openstreetmap-carto.lua" + \
            " --style /home/osm/openstreetmap-carto/openstreetmap-carto.style -C 200 -U osm "+ tmpname)
        os.unlink(tmpname)  #Finished with temporary osm data file - delete.

        if p['contour'] == "SRTM" or p['contour'] == "COPE":
            #Now get contours using phyghtmap:
            #Use Phyghtmap just to get DEM data - process separately
            if p['contour'] == "SRTM":
                sourceText = " --source=srtm1 --srtm-version=3 "
            else:
                sourceText = " --source=cope1"
            phyString="phyghtmap --area="+str(bbox2.minx-0.0002)+":"+str(bbox2.miny-0.0002)+":"+ \
                str(bbox2.maxx+0.0002)+":"+str(bbox2.maxy+0.0002)+ sourceText + \
                " --earthexplorer-user="******" --earthexplorer-password="******" --hgtdir=" + home_base + "/hgt -p " + home_base + "/"+tmpid + " >> " + home_base + "/phy.log"
            os.system(phyString)
            #Merge file(s) into single virtual dataset (prevents contour boundaries at degree grid lines)
            os.system("gdalbuildvrt " + home_base + "/" + tmpid + "_a.vrt " +
                      home_base + "/" + tmpid + "_lon*")
            #Resample at 10m intervals to get smoother contours.  Reproject at the same time
            os.system("gdalwarp -r cubic -tr 10 10 -s_srs EPSG:4326 -t_srs EPSG:3857 -te_srs EPSG:4326 -te " + \
                str(bbox2.minx-0.0001)+" "+str(bbox2.miny-0.0001)+" "+ \
                str(bbox2.maxx+0.0001)+" "+str(bbox2.maxy+0.0001)+ \
                " -of SAGA -ot Float32 " + home_base + "/"+tmpid + "_a.vrt " + home_base + "/"+tmpid + ".sdat")
            #Apply Guassian blur to further smooth contours
            os.system(
                "saga_cmd grid_filter \"Gaussian Filter\" -SIGMA 2.0 -RADIUS 12 -INPUT "
                + home_base + "/" + tmpid + ".sdat -RESULT " + home_base +
                "/" + tmpid + "_s")
            #Generate contours
            os.system("gdal_contour -b 1 -a height -i " + p['interval'] + " " +
                      home_base + "/" + tmpid + "_s.sdat " + home_base + "/" +
                      tmpid + ".shp")
            # If contour generation failed, use a dummy dataset so that the DB table
            # gets created and the SQL query returns without errors.
            try:
                os.stat(home_base + "/" + tmpid + ".shp")
            except:
                os.system("cp " + home_base + "/null.shp " + home_base + "/" +
                          tmpid + ".shp")
                os.system("cp " + home_base + "/null.shx " + home_base + "/" +
                          tmpid + ".shx")
                os.system("cp " + home_base + "/null.dbf " + home_base + "/" +
                          tmpid + ".dbf")
            #then load contours to database
            os.system("shp2pgsql -g way -s 3857 " + home_base + "/" + tmpid +
                      ".shp " + tmpid +
                      "_srtm_line | psql -h localhost -p 5432 -U osm -d otf1")
            contour_table = tmpid + "_srtm_line"
            import glob
            for i in glob.glob(home_base + '/' + tmpid + '*'):
                os.unlink(i)  #Finished with temporary files - delete.
    else:  # If SRTM or COPE contours, still need to point to correct contour table for reused data so:
        if p['contour'] == "SRTM" or p['contour'] == "COPE":
            contour_table = tmpid + "_srtm_line"

    # Need a custom Mapnik style file to find tables with temo id prefix.
    # Therefore inject "prefix" entity into appropriate base style definition and save using temp id as name.
    if p.get(
            'drives', "no"
    ) != "no":  #Render driveways as near-transparent if not selected.  Allows recovery later if needed.
        driveway_colour = "#010101FF"
    else:
        driveway_colour = "#01010101"

    import re
    insertstring="%settings;\n<!ENTITY prefix \"" + tmpid + "\">" + \
        "\n<!ENTITY driveway \"" + driveway_colour + "\">" + \
        "\n<!ENTITY rail \"" + ("yes" if p.get('rail',"yes") != "no" else "no") + "\">" + \
        "\n<!ENTITY walls \"" + ("yes" if p.get('walls',"yes") != "no" else "no") + "\">" + \
        "\n<!ENTITY trees \"" + ("yes" if p.get('trees',"yes") != "no" else "no") + "\">" + \
        "\n<!ENTITY hedges \"" + ("yes" if p.get('hedges',"yes") != "no" else "no") + "\">" + \
        "\n<!ENTITY fences \"" + ("yes" if p.get('fences',"yes") != "no" else "no") + "\">" + \
        "\n<!ENTITY lidartable \"" + contour_table + "\">" + \
        "\n<!ENTITY contourSeparation \"" + p['interval'] + "\">" + \
        "\n<!ENTITY layers-contours SYSTEM \"inc/layers_contours_" + p['contour'] + ".xml.inc\">"
    searchstring = "\%settings;"
    styleString = re.sub(searchstring, insertstring, styleString)

    with open(styleFile, mode="w") as f:
        f.write(styleString)

    cbbox = mapnik.Box2d(mapWLon, mapSLat, mapELon, mapNLat)
    # Limit the size of map we are prepared to produce to roughly A2 size.
    if PAPER_W * PAPER_H > 0.25 and style != "adhoc":
        return "Map too large. Try increasing the scale value or using a smaller paper size."

    if scale > 50000 and style != "adhoc":
        return "Scale too small. Try using a lower scale value."

    # Calculate scale, for scale bar and grid lines
    scaleBarMetres = 500
    if scale < 10000:
        scaleBarMetres = 200
    scaleBarW = scaleBarMetres / float(scale)

    # Create map
    map = mapnik.Map(int(EXTENT_W * S2P), int(EXTENT_H * S2P))

    # Load map configuration
    mapnik.load_map(map, styleFile)

    # Zoom the map to the Gbounding box
    map.zoom_to_box(cbbox)

    file = tempfile.NamedTemporaryFile()

    surface = None
    if fileformat == 'jpg' or fileformat == 'pre':
        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(PAPER_W * S2P),
                                     int(PAPER_H * S2P))
    elif fileformat == 'svg':
        surface = cairo.SVGSurface(file.name, PAPER_W * S2P / SCALE_FACTOR,
                                   PAPER_H * S2P / SCALE_FACTOR)
        surface.set_device_scale(1.0 / SCALE_FACTOR, 1.0 / SCALE_FACTOR)
        versions = surface.get_versions()
        version = versions[1]
        surface.restrict_to_version(version)
    else:
        surface = cairo.PDFSurface(file.name, PAPER_W * S2P / SCALE_FACTOR,
                                   PAPER_H * S2P / SCALE_FACTOR)
        surface.set_device_scale(1.0 / SCALE_FACTOR, 1.0 / SCALE_FACTOR)

    # Adornments - Title swoosh back
    ctx = cairo.Context(surface)
    ctx.translate(0, 0)
    ctx.set_line_width(1 * SCALE_FACTOR)
    ctx.move_to(0, 0)
    ctx.rel_line_to(0, 0.25 * PAPER_H * S2P)
    ctx.rel_line_to(0.2 * PAPER_W * S2P, 0)
    ctx.rel_line_to(0.4 * PAPER_W * S2P, -0.25 * PAPER_H * S2P)
    ctx.close_path()
    ctx.set_source_rgb(0.91, 0.15, 0.28)
    if style != 'blueprint':
        ctx.fill()

    #Adornments - Attrib swoosh back
    ctx = cairo.Context(surface)
    ctx.translate(0, 0)
    ctx.set_line_width(1 * SCALE_FACTOR)
    ctx.move_to(PAPER_W * S2P, PAPER_H * S2P)
    ctx.rel_line_to(0, -0.25 * PAPER_H * S2P)
    ctx.rel_line_to(-0.2 * PAPER_W * S2P, 0)
    ctx.rel_line_to(-0.4 * PAPER_W * S2P, 0.25 * PAPER_H * S2P)
    ctx.close_path()
    ctx.set_source_rgb(0.12, 0.5, 0.65)
    if style != "blueprint":
        ctx.fill()

    # Background map
    ctx = cairo.Context(surface)
    ctx.set_operator(cairo.Operator.OVER)
    ctx.translate(MAP_WM * S2P, MAP_NM * S2P)
    ctx.rectangle(0, 0, MAP_W * S2P, MAP_H * S2P)
    ctx.clip()  #Clip to map area
    ctx.save()
    ctx.translate(MAP_W * S2P / 2,
                  MAP_H * S2P / 2)  # translate origin to the center
    ctx.rotate(rotation)
    ctx.translate(-EXTENT_W * S2P / 2, -EXTENT_H * S2P / 2)

    mapnik.render(map, ctx, SCALE_FACTOR, 0, 0)

    ctx.restore()

    if style == "adhoc":
        ctx = cairo.Context(surface)
        ctx.set_source_rgb(1, 1, 1)
        ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL,
                             cairo.FONT_WEIGHT_NORMAL)
        ctx.set_font_size(0.5 * SCALE_FACTOR)
        text = path
        ctx.translate(MAP_WM * S2P, (MAP_NM + MAP_H + 0.001) * S2P)
        ctx.show_text(text)
        if fileformat == 'jpg' or fileformat == 'pre':
            surface.write_to_png(file.name)
        else:
            surface.finish()
        return file

    #  Add grid lines in same layer - allows darken comp-op
    if style != "blueprint" and p.get('grid', "yes") != "no":
        ctx.set_source_rgb(0.5, 0.5, 1)
        ctx.set_operator(cairo.Operator.MULTIPLY)
        ctx.set_line_width(0.5 * SCALE_FACTOR)
        northSpacing = scaleBarW / math.cos(magdec * math.pi / 180 + rotation)
        shift = MAP_H * S2P * math.tan(magdec * math.pi / 180 + rotation) * 0.5
        lines = range(int(-northSpacing * S2P / 2),
                      int((MAP_W + northSpacing) * S2P),
                      int(northSpacing * S2P))
        for line in lines:
            ctx.move_to(line + shift, 0)
            ctx.line_to(line - shift, MAP_H * S2P)
        ctx.stroke()

    # Start control
    if slon != 0 and slat != 0:
        ctx = cairo.Context(surface)
        ctx.set_operator(cairo.Operator.MULTIPLY)
        ctx.set_source_rgb(0.651, 0.149, 1)
        ctx.translate(MAP_WM * S2P + MAP_W * S2P / 2, MAP_NM * S2P +
                      MAP_H * S2P / 2)  # translate origin to the center
        ctx.rotate(rotation)  # rotate map to correct angle
        ctx.translate(-EXTENT_W * S2P / 2,
                      -EXTENT_H * S2P / 2)  # set origin to NW corner
        ctx.set_line_width(SC_T * S2P)
        ctx.set_line_join(cairo.LINE_JOIN_ROUND)
        ctx.translate((slon - mapWLon) * EXTENT_W * S2P / (mapELon - mapWLon),
                      (mapNLat - slat) * EXTENT_H * S2P / (mapNLat - mapSLat))
        startRot = 0  #rotation of start triangle - if linear course and at least 1 control
        if len(controlsArr) > 0 and p.get('linear', "no") != "no":
            startRot = math.atan2(
                slat - float(controlsArr[2]),
                float(controlsArr[3]) - slon) - math.pi / 6 + rotation
        ctx.rotate(startRot - rotation)
        ctx.move_to(0, -0.577 * SC_W * S2P)
        ctx.rel_line_to(-0.5 * SC_W * S2P, 0.866 * SC_W * S2P)
        ctx.rel_line_to(SC_W * S2P, 0)
        ctx.close_path()
        ctx.stroke()
        ctx.rotate(-startRot)
        #Finish control (same place as start, unless separate finish coords)
        if flon != 0 and flat != 0:
            ctx.rotate(rotation)
            ctx.translate((flon - slon) * EXTENT_W * S2P / (mapELon - mapWLon),
                          (slat - flat) * EXTENT_H * S2P / (mapNLat - mapSLat))
        ctx.set_line_width(C_T * S2P)
        ctx.arc(0, 0, C_R * S2P * 1.2, 0, 2 * math.pi)  #Outer circle
        ctx.stroke()
        ctx.arc(0, 0, C_R * S2P * 0.8, 0, 2 * math.pi)  #inner circle
        ctx.stroke()

    # Controls and labels
    if len(controlsArr) > 0:
        ctx = cairo.Context(surface)
        ctx.translate(MAP_WM * S2P + MAP_W * S2P / 2, MAP_NM * S2P +
                      MAP_H * S2P / 2)  # translate origin to the center
        ctx.rotate(rotation)
        ctx.translate(-EXTENT_W * S2P / 2, -EXTENT_H * S2P / 2)
        ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL,
                             cairo.FONT_WEIGHT_NORMAL)
        ctx.set_font_size(CTEXT_S * S2P)
        numControls = len(controlsArr) // 4
        #Draw white halo around control numbers for legibility on complex maps
        ctx.set_operator(cairo.Operator.SOURCE)
        ctx.set_source_rgb(1, 0.997, 1)
        for i in range(numControls):
            text = controlsArr[4 * i]
            labelAngle = float(controlsArr[4 * i + 1])
            controllat = float(controlsArr[4 * i + 2])
            controllon = float(controlsArr[4 * i + 3])
            controllatP = (mapNLat - controllat) * EXTENT_H / (mapNLat -
                                                               mapSLat)
            controllonP = (controllon - mapWLon) * EXTENT_W / (mapELon -
                                                               mapWLon)
            x_bearing, y_bearing, width, height = ctx.text_extents(text)[:4]
            labelX = C_R * 2.5 * math.sin(math.pi * labelAngle / 180)
            labelY = C_R * 2.5 * math.cos(math.pi * labelAngle / 180)
            ctx.save()
            ctx.translate(controllonP * S2P, controllatP * S2P)
            ctx.rotate(-rotation)
            ctx.move_to(labelX * S2P - width / 2, -labelY * S2P + height / 2)
            ctx.text_path(text)
            ctx.set_line_width(C_T * S2P)
            ctx.stroke_preserve()
            ctx.fill()
            ctx.restore()

        ctx.set_source_rgb(0.651, 0.149, 1)
        ctx.set_operator(cairo.Operator.MULTIPLY)
        lastlonP = (slon - mapWLon) * EXTENT_W / (mapELon - mapWLon)
        lastlatP = (mapNLat - slat) * EXTENT_H / (mapNLat - mapSLat)
        for i in range(numControls):
            text = controlsArr[4 * i]
            labelAngle = float(controlsArr[4 * i + 1])
            controllat = float(controlsArr[4 * i + 2])
            controllon = float(controlsArr[4 * i + 3])
            controllatP = (mapNLat - controllat) * EXTENT_H / (mapNLat -
                                                               mapSLat)
            controllonP = (controllon - mapWLon) * EXTENT_W / (mapELon -
                                                               mapWLon)
            ctx.move_to((controllonP + C_R) * S2P, controllatP * S2P)
            ctx.set_line_width(C_T * S2P)
            ctx.arc(controllonP * S2P, controllatP * S2P, C_R * S2P, 0,
                    2 * math.pi)
            ctx.stroke()
            ctx.move_to((controllonP + CDOT_R) * S2P, controllatP * S2P)
            ctx.arc(controllonP * S2P, controllatP * S2P, CDOT_R * S2P, 0,
                    2 * math.pi)
            ctx.fill()
            if p.get('linear', "no") != "no":
                angle = math.atan2((controllatP - lastlatP),
                                   (controllonP - lastlonP))
                start2lonP = lastlonP + math.cos(angle) * C_R * (1.3 if i == 0
                                                                 else 1.0)
                start2latP = lastlatP + math.sin(angle) * C_R * (1.3 if i == 0
                                                                 else 1.0)
                end2lonP = controllonP - math.cos(angle) * C_R
                end2latP = controllatP - math.sin(angle) * C_R
                ctx.move_to(start2lonP * S2P, start2latP * S2P)
                ctx.line_to(end2lonP * S2P,
                            end2latP * S2P)  #draw line between controls
                lastlonP = controllonP
                lastlatP = controllatP

            x_bearing, y_bearing, width, height = ctx.text_extents(text)[:4]
            labelX = C_R * 2.5 * math.sin(math.pi * labelAngle / 180)
            labelY = C_R * 2.5 * math.cos(math.pi * labelAngle / 180)
            ctx.save()
            ctx.translate(controllonP * S2P, controllatP * S2P)
            ctx.rotate(-rotation)
            ctx.move_to(labelX * S2P - width / 2, -labelY * S2P + height / 2)
            ctx.show_text(text)
            ctx.restore()
        # draw line from last control to finish
        if p.get('linear', "no") != "no":
            controllatP = (mapNLat - flat) * EXTENT_H / (mapNLat - mapSLat)
            controllonP = (flon - mapWLon) * EXTENT_W / (mapELon - mapWLon)
            angle = math.atan2((controllatP - lastlatP),
                               (controllonP - lastlonP))
            start2lonP = lastlonP + math.cos(angle) * C_R
            start2latP = lastlatP + math.sin(angle) * C_R
            end2lonP = controllonP - math.cos(angle) * C_R * 1.2
            end2latP = controllatP - math.sin(angle) * C_R * 1.2
            ctx.move_to(start2lonP * S2P, start2latP * S2P)
            ctx.line_to(end2lonP * S2P, end2latP * S2P)
        ctx.stroke()

    # Crosses and labels
    if len(crossesArr) > 0:
        #ctx = cairo.Context(surface)
        ctx.set_source_rgb(0.651, 0.149, 1)
        ctx.set_operator(cairo.Operator.MULTIPLY)
        ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL,
                             cairo.FONT_WEIGHT_BOLD)
        ctx.set_font_size(CTEXT_S * S2P / 1.5)
        #ctx.set_source_rgb(1, 0, 0)
        numCrosses = len(crossesArr) // 2
        for i in range(numCrosses):
            text = "X"
            controllat = float(crossesArr[2 * i])
            controllon = float(crossesArr[2 * i + 1])
            controllatP = (mapNLat - controllat) * EXTENT_H / (mapNLat -
                                                               mapSLat)
            controllonP = (controllon - mapWLon) * EXTENT_W / (mapELon -
                                                               mapWLon)
            x_bearing, y_bearing, width, height = ctx.text_extents(text)[:4]
            ctx.move_to((controllonP) * S2P - width / 2,
                        (controllatP) * S2P + height / 2)
            ctx.show_text(text)

    #Crossing points and labels
    if len(cpsArr) > 0:
        #ctx = cairo.Context(surface)
        ctx.set_source_rgb(0.651, 0.149, 1)
        ctx.set_operator(cairo.Operator.DARKEN)
        ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL,
                             cairo.FONT_WEIGHT_NORMAL)
        ctx.set_font_size(CTEXT_S * S2P / 1.1)
        #ctx.set_source_rgb(1, 0, 0)
        numCps = len(cpsArr) // 3
        for i in range(numCps):
            text = "]["
            controlAngle = float(cpsArr[3 * i])
            controllat = float(cpsArr[3 * i + 1])
            controllon = float(cpsArr[3 * i + 2])
            controlAngleRads = math.pi * controlAngle / 180
            controllatP = (mapNLat - controllat) * EXTENT_H / (mapNLat -
                                                               mapSLat)
            controllonP = (controllon - mapWLon) * EXTENT_W / (mapELon -
                                                               mapWLon)
            x_bearing, y_bearing, width, height, x_advance, y_advance = ctx.text_extents(
                text)[:6]
            ctx.move_to((controllonP) * S2P, (controllatP) * S2P)
            ctx.rotate(controlAngleRads)
            ctx.rel_move_to(-width / 2, height / 3.5)
            ctx.show_text(text)
            ctx.rotate(-1.0 * controlAngleRads)
            #ctx.save()

    # Adornments - Title
    ctx = cairo.Context(surface)
    ctx.select_font_face("Arial", cairo.FONT_SLANT_ITALIC,
                         cairo.FONT_WEIGHT_NORMAL)
    if style == 'blueprint':
        ctx.select_font_face("Impact", cairo.FONT_SLANT_NORMAL,
                             cairo.FONT_WEIGHT_NORMAL)
    text = unquote(title)

    if len(text) > 26:
        ctx.set_font_size(15 * SCALE_FACTOR)
    elif len(text) > 18:
        ctx.set_font_size(18 * SCALE_FACTOR)
    else:
        ctx.set_font_size(21 * SCALE_FACTOR)
    ctx.translate((MAP_WM + 0.014) * S2P,
                  (MAP_NM - ADORN_TITLE_SM) * S2P)  #add space to left for logo

    if style == 'blueprint':
        ctx.set_source_rgb(0, 0.5, 0.8)
    else:
        ctx.set_source_rgb(1, 1, 1)
    ctx.show_text(text.upper())

    # Adornments - Scale Text
    ctx = cairo.Context(surface)
    ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL,
                         cairo.FONT_WEIGHT_NORMAL)
    text = "scale 1:" + str(scale)

    if style == "oterrain" or style == "streeto":
        text = "scale 1:" + str(scale) + ", contours " + p['interval'] + "m"
    ctx.set_source_rgb(0, 0, 0)
    if style == 'blueprint':
        ctx.set_source_rgb(0, 0.5, 0.8)

    ctx.set_font_size(11 * SCALE_FACTOR)
    width = ctx.text_extents(text)[4]
    ctx.translate(
        (MAP_WM + MAP_W) * S2P - width - (ADORN_ARROW_W + ADORN_LOGO_W) * S2P,
        (MAP_NM - ADORN_SCALE_SM) * S2P)
    ctx.show_text(text)

    # Adornments - Scale Bar and Caption
    ctx = cairo.Context(surface)
    ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL,
                         cairo.FONT_WEIGHT_NORMAL)
    text = str(scaleBarMetres) + "m"
    ctx.set_source_rgb(0, 0, 0)
    if style == 'blueprint':
        ctx.set_source_rgb(0, 0.5, 0.8)
    ctx.set_font_size(7 * SCALE_FACTOR)
    width = ctx.text_extents(text)[4]
    barCaptionX = (MAP_WM + MAP_W -
                   (ADORN_ARROW_W + ADORN_LOGO_W)) * S2P - width
    ctx.translate(barCaptionX, (MAP_NM - ADORN_SCALEBAR_SM) * S2P)
    ctx.show_text(text)
    ctx.set_line_width(0.5 * SCALE_FACTOR)

    ctx.move_to((-scaleBarW - ADORN_SCALEBAR_PADDING) * S2P, 0)
    ctx.rel_line_to(0, -ADORN_SCALEBAR_LARGETICK * S2P)
    ctx.rel_line_to(0, ADORN_SCALEBAR_LARGETICK * S2P)
    ctx.rel_line_to(scaleBarW * S2P / 2, 0)
    ctx.rel_line_to(0, -ADORN_SCALEBAR_SMALLTICK * S2P)
    ctx.rel_line_to(0, ADORN_SCALEBAR_SMALLTICK * S2P)
    ctx.rel_line_to(scaleBarW * S2P / 2, 0)
    ctx.rel_line_to(0, -ADORN_SCALEBAR_LARGETICK * S2P)
    ctx.stroke()

    # Adornments - North Arrow
    ctx = cairo.Context(surface)
    ctx.translate((MAP_WM + MAP_W - ADORN_LOGO_W) * S2P - width,
                  (CONTENT_NM + 0.004) * S2P)  #set to centre of symbol...
    ctx.rotate(
        magdec * math.pi / 180 +
        rotation)  #so that rotation doesn't add translation.  Point to mag. N
    ctx.set_line_width(1 * SCALE_FACTOR)
    ctx.set_source_rgb(0, 0, 0)
    if style == 'blueprint':
        ctx.set_source_rgb(0, 0.5, 0.8)
    ctx.move_to(0, -0.004 * S2P)
    ctx.line_to(0.001 * S2P, -0.002 * S2P)
    ctx.line_to(-0.001 * S2P, -0.002 * S2P)
    ctx.close_path()
    ctx.fill()
    ctx.move_to(0, -0.003 * S2P)
    ctx.line_to(0, 0.004 * S2P)
    ctx.stroke()
    ctx.set_line_join(cairo.LINE_JOIN_ROUND)
    ctx.set_line_cap(cairo.LINE_CAP_ROUND)
    ctx.move_to(-0.001 * S2P, 0.001 * S2P)
    ctx.rel_line_to(0, -0.002 * S2P)
    ctx.rel_line_to(0.002 * S2P, 0.002 * S2P)
    ctx.rel_line_to(0, -0.002 * S2P)
    ctx.stroke()

    # Adornments - Logo
    if style != "blueprint":
        logoSurface = cairo.ImageSurface.create_from_png(home +
                                                         "/images/oflogo.png")
        ctx = cairo.Context(surface)
        width = logoSurface.get_width() * ADORN_LOGO_SCALE
        ctx.translate((MAP_WM + MAP_W) * S2P - width, CONTENT_NM * S2P)
        ctx.scale(ADORN_LOGO_SCALE, ADORN_LOGO_SCALE)
        ctx.set_source_surface(logoSurface, 0, 0)
        ctx.paint()

    # Adornments - Attribution left line 1
    ctx = cairo.Context(surface)
    ctx.select_font_face("Arial", cairo.FONT_SLANT_ITALIC,
                         cairo.FONT_WEIGHT_NORMAL)
    ctx.set_source_rgb(0.12, 0.5, 0.65)
    if style == 'blueprint':
        ctx.set_source_rgb(0, 0.5, 0.8)

    ctx.set_font_size(7 * SCALE_FACTOR)
    text = "Map data: © OpenStreetMap contributors; Open Database Licence."
    ctx.translate((MAP_WM) * S2P, (MAP_NM + MAP_H + ADORN_ATTRIB_NM) * S2P)
    ctx.show_text(text)

    # Adornments - Attribution left line 2 - contours
    ctx = cairo.Context(surface)
    ctx.select_font_face("Arial", cairo.FONT_SLANT_ITALIC,
                         cairo.FONT_WEIGHT_NORMAL)
    ctx.set_source_rgb(0.12, 0.5, 0.65)
    ctx.set_font_size(7 * SCALE_FACTOR)

    ctx.translate((MAP_WM) * S2P,
                  (MAP_NM + MAP_H + ADORN_ATTRIB_NM + 0.002) * S2P)
    ctx.show_text(contour_text)
    cur.close()
    conn.close()

    #Adornments - Attribution left line 3
    ctx = cairo.Context(surface)
    ctx.select_font_face("Arial", cairo.FONT_SLANT_ITALIC,
                         cairo.FONT_WEIGHT_NORMAL)
    ctx.set_source_rgb(0.12, 0.5, 0.65)
    if style == "blueprint":
        ctx.set_source_rgb(0, 0.5, 0.8)

    ctx.set_font_size(7 * SCALE_FACTOR)
    text = "OOM created by Oliver O'Brien. Make your own: " + web_root
    ctx.translate((MAP_WM) * S2P,
                  (MAP_NM + MAP_H + ADORN_ATTRIB_NM + 0.004) * S2P)
    ctx.show_text(text)

    #Adornments - Attribution right line 1
    if style == "oterrain" or style == "streeto" or style == "blueprint":
        ctx = cairo.Context(surface)
        ctx.select_font_face("Arial", cairo.FONT_SLANT_ITALIC,
                             cairo.FONT_WEIGHT_BOLD)
        ctx.set_source_rgb(1, 1, 1)
        if style == "blueprint":
            ctx.set_source_rgb(0, 0.5, 0.8)
        ctx.set_font_size(9 * SCALE_FACTOR)
        text = "OOM v3 developed with a grant from the Orienteering Foundation"
        width = ctx.text_extents(text)[4]
        ctx.translate((MAP_WM + MAP_W) * S2P - width,
                      (MAP_NM + MAP_H + ADORN_ATTRIB_NM) * S2P)
        ctx.show_text(text)

    #Attribution right line 2
    ctx = cairo.Context(surface)
    ctx.select_font_face("Arial", cairo.FONT_SLANT_ITALIC,
                         cairo.FONT_WEIGHT_NORMAL)
    ctx.set_source_rgb(1, 1, 1)
    if style == "blueprint":
        ctx.set_source_rgb(0, 0.5, 0.8)
    ctx.set_font_size(9 * SCALE_FACTOR)
    text = "Map ID: " + mapid
    width = ctx.text_extents(text)[4]
    ctx.translate((MAP_WM + MAP_W) * S2P - width,
                  (MAP_NM + MAP_H + ADORN_ATTRIB_NM + ADORN_ATTRIB_NM) * S2P)
    ctx.show_text(text)

    # Adornments - URL
    ctx = cairo.Context(surface)
    ctx.select_font_face("Arial", cairo.FONT_SLANT_NORMAL,
                         cairo.FONT_WEIGHT_NORMAL)
    ctx.set_font_size(0.5 * SCALE_FACTOR)
    text = web_root + "render/" + fileformat + '/?' + path
    ctx.translate(MAP_WM * S2P, (MAP_NM + MAP_H + ADORN_URL_NM) * S2P)
    ctx.show_text(text)

    if fileformat == 'jpg' or fileformat == 'pre':
        from PIL import Image, ImageCms
        surface.write_to_png(file.name + '.png')
        im = Image.open(file.name + '.png')
        bg = Image.new("RGB", im.size, (255, 255, 255))
        profile = ImageCms.createProfile("sRGB")
        profile2 = ImageCms.ImageCmsProfile(profile)

        bg.paste(im, im)
        bg.save(file.name, 'JPEG', quality=95, icc_profile=profile2.tobytes())
    else:
        surface.finish()
        surface.flush()
    if fileformat == 'pdf':
        # Add Geospatial PDF metadata
        map_bounds = (MAP_WM / PAPER_W, (PAPER_H - MAP_SM) / PAPER_H, MAP_WM /
                      PAPER_W, MAP_NM / PAPER_H, (PAPER_W - MAP_EM) / PAPER_W,
                      MAP_NM / PAPER_H, (PAPER_W - MAP_EM) / PAPER_W,
                      (PAPER_H - MAP_SM) / PAPER_H)
        file2 = tempfile.NamedTemporaryFile()
        file = add_geospatial_pdf_header(map,
                                         file,
                                         file2,
                                         map_bounds,
                                         pagePoly,
                                         epsg=3857)

    #Delete temporary style file and postgres tables (everything beginning with "h"):
    #BUT - don't delete here as may be needed for related query.  Periodically clean out with cron job instead
    #os.unlink(styleFile)
    #dropTables = 'psql -U osm otf1 -t -c "select \'drop table \\"\' || tablename || \'\\" cascade;\' from pg_tables where schemaname = \'public\' and tablename like \'h%\'"  | psql -U osm otf1'
    #os.system(dropTables)

    return file
예제 #28
0
 def get_image_profile(self, image):
     try:
         return ImageCms.ImageCmsProfile(
             io.BytesIO(image.info.get('icc_profile')))
     except:
         return None
예제 #29
0
def generate(cut_type, choice, fabric_type):
    ''' Based on the size choice, select a template. '''
    template = Image.open(path1 + str(choice)+'-'+ randomTemplateString +template_type).convert(convert_mode)
    
    ''' Saving metadata from template to result file '''
    ''' Exif Loaded to readable format '''
    exif_dict = piexif.load(template.info.get('exif'))
    ''' ICC Profile '''
    if (fabric_mode != "CMYK"):
        icc_profile = template.info.get('icc_profile')
    else:
        icc_profile = ImageCms.ImageCmsProfile(io.BytesIO(template.info.get('icc_profile')))
    ''' Changes "Program name" attribute '''
    exif_dict["0th"][305] = GENERATOR_NAME
    ''' Resets thumbnail to later get automatically generated '''
    exif_dict.pop('thumbnail', None)
    ''' Dumpted to bytes '''
    exif_bytes = piexif.dump(exif_dict)

    ''' Draw variable. '''
    draw = ImageDraw.Draw(template)

    ''' Upper stamp '''
    
    ''' Stamp seperator. '''
    draw.text((SEP1, 23), "|", font=font, fill=TEXT_COLOR)
    draw.text((SEP2, 23), "|", font=font, fill=TEXT_COLOR)
    draw.text((SEP1, 35), "|", font=font, fill=TEXT_COLOR)
    draw.text((SEP2, 35), "|", font=font, fill=TEXT_COLOR)
    ''' Safety spray '''
    draw.text((5453, -10), "|", font=font, fill=TEXT_COLOR)

    ''' Left section '''
    ''' Pasting icons. '''
    template.paste(cmyk, (CMYK_X, CMYK_Y), cmyk)
    template.paste(logo, (LOGO_X, LOGO_Y), logo)
    template.paste(handIcon, (HAND_X,HAND_Y), handIcon)
    ''' Website stamp. '''
    draw.text((HAND_X-len(WEBSITE_LINK)*CHAR_WIDTH-25, LINE1_Y), WEBSITE_LINK , font=font, fill=TEXT_COLOR)
    ''' Fabric ID stamp '''
    draw.text((HAND_X-len(FABRIC_ID)*CHAR_WIDTH-25, LINE2_Y), FABRIC_ID , font=font, fill=TEXT_COLOR)
    ''' Fabric name '''
    if len(fabric_name) > 22:
        draw.text((NAME_X, LINE1_Y), fabric_name_1, font=font, fill=TEXT_COLOR)
        draw.text((NAME_X, LINE2_Y), fabric_name_2, font=font, fill=TEXT_COLOR)
    else:
        draw.text((NAME_X, LINE_Y), fabric_name, font=font, fill=TEXT_COLOR)
    ''' Client link if provided '''
    ''' Fabric type '''
    fabric_type_name = returnFabricTypeName(fabric_type).replace('-', ' ')
    if len(CLIENT_LINK) != 0:
        draw.text((HALF_WIDH-(len(fabric_type_name)*CHAR_WIDTH)-75, LINE1_Y), fabric_type_name, font=font, fill=TEXT_COLOR)
        draw.text((HALF_WIDH-(len(CLIENT_LINK)*CHAR_WIDTH)-62, LINE2_Y), CLIENT_LINK, font=font, fill=TEXT_COLOR)
    else:
        draw.text((HALF_WIDH-(len(fabric_type_name)*CHAR_WIDTH)-35, LINE_Y), fabric_type_name, font=font, fill=TEXT_COLOR)


    ''' Right section '''
    fabric_type_name = returnFabricTypeName(fabric_type).replace('-', ' ')
    if len(CLIENT_LINK) != 0:
        draw.text((HALF_WIDH+80, LINE1_Y), fabric_type_name, font=font, fill=TEXT_COLOR)
        draw.text((HALF_WIDH+80, LINE2_Y), CLIENT_LINK, font=font, fill=TEXT_COLOR)
    else:
        draw.text((HALF_WIDH+80, LINE_Y), fabric_type_name, font=font, fill=TEXT_COLOR)

    template.paste(logo, (WIDTH-LOGO_X-100, LOGO_Y), logo)
    template.paste(handIcon, (WIDTH-HAND_X-85,HAND_Y), handIcon)
    draw.text((WIDTH-HAND_X+60, LINE1_Y), WEBSITE_LINK, font=font, fill=TEXT_COLOR)
    draw.text((WIDTH-HAND_X+60, LINE2_Y), FABRIC_ID , font=font, fill=TEXT_COLOR)
    if len(fabric_name) > 22:
        draw.text((WIDTH-LOGO_X-125-(len(fabric_name_1)*CHAR_WIDTH), LINE1_Y), fabric_name_1, font=font, fill=TEXT_COLOR)
        draw.text((WIDTH-LOGO_X-125-(len(fabric_name_2)*CHAR_WIDTH), LINE2_Y), fabric_name_2, font=font, fill=TEXT_COLOR)
    else:
        draw.text((WIDTH-LOGO_X-125-(len(fabric_name)*CHAR_WIDTH), LINE_Y), fabric_name, font=font, fill=TEXT_COLOR)

    ''' Lower stamp '''
    uppper_stamp = template.crop((0,0,WIDTH,150)).convert(convert_mode).rotate(180)
    template.paste(uppper_stamp, (0, 8250), uppper_stamp) if choice == 56 else template.paste(uppper_stamp, (0, 6150), uppper_stamp)

    ''' Resizing and pasting fabric '''
    for fabPart in returnFabricParts(cut_type, choice, SEAMLESS, INCHES, HEADER):
        if (keep_ratio):
            '''specialFabric = fillColor.resize(fabPart.size)
            template.paste(specialFabric, fabPart.location, specialFabric)'''

            ''' Resize image with keeping ratio and pasting it on the space filler ''' 
            ''' Get new sizes and locations '''
            x = fabPart.location[0]
            y = fabPart.location[1]
            nx = x
            ny = y

            w = fabPart.size[0]
            h = fabPart.size[1]
            nw = w
            nh = h
            m = min(w,h)
            if m == w:
                nh = nw * (fabric.size[1] / fabric.size[0])
                ny = y + ((h - nh)//2)
            else:
                nw = nh * (fabric.size[0] / fabric.size[1])
                nx = x + ((w - nw)//2)

            newLocation = (int(nx),int(ny))
            specialSize = (int(nw),int(nh))
            fabricSize = fabric.resize(specialSize)
            template.paste(fabricSize, newLocation, fabricSize)
        else:
            fabricSize = fabric.resize(fabPart.size)
            template.paste(fabricSize, fabPart.location, fabricSize)

    ''' Create image final structured name '''
    file_name = fabric_name.replace(' ', '_')+"-"+cut_type+"-"+returnFabricTypeName(fabric_type)+"-36x"+str(choice)+".jpg"

    ''' Finally save the image as JPEG '''
    if (fabric_mode != "CMYK"):
        template.convert(fabric_mode).save(path2 + fabric_name.replace(' ', '_')+'/'+file_name, "JPEG", dpi=(150,150), exif=exif_bytes, quality=100, icc_profile=icc_profile, optimize=False)
    else:
        template = ImageCms.profileToProfile(template, icc_profile, 'sRGB Color Space Profile.icm', renderingIntent=0, outputMode='RGB')
        template.save(path2 + fabric_name.replace(' ', '_')+'/'+file_name, "JPEG", dpi=(150,150), exif=exif_bytes, quality=100, optimize=False)
예제 #30
0
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import os
from PyBundle import bundle_dir

GRAY_PATH = os.path.join(bundle_dir(), 'Gray-CIE_L.icc')

try:
    from PIL import ImageCms
    gray = ImageCms.ImageCmsProfile(GRAY_PATH)
    sRGB = ImageCms.createProfile('sRGB')
except ImportError:
    gray = None
    sRGB = None