2
\$\begingroup\$

In this challenge, you will take an input image and crop it to make a square, leaving only the central part of the input image.

The answers must work with landscape, portrait, or square input images. The width of the output image should be equal to the shortest side of the input image e.g. the result of a 200x300 image should be a 200x200 image. An equal amount must be cropped from both ends of the input image e.g. for a 200x300 input image, 50 pixels should be cropped from each end of the image, leaving only the central part of the image. Where its not possible to crop an equal amount from both sides e.g. a 200x301 image, the extra 1 pixel may be cropped from either end of the image. For a square input image, the output should be identical to the input.

Input:

The input file should be read from the hard disk of the machine running the program. You may choose which of bmp, jpeg, png or pnm to support as the input image format.

Output

The output file should be written to the hard disk of the machine running the program. The format of the output image should be bmp, jpeg, png or pnm.

Win Criteria

This is code golf. Shortest code wins.

Notes:

  1. The input/output file paths do not count towards the program size.
  2. The standard libraries of your programming language may be used. Other libraries are disallowed.
  3. Standard loopholes apply.

Example:

Input Image:

BG1

Expected Output:

BG2

Images by kirill777 - licenced CC-BY 3.0

\$\endgroup\$
  • 3
    \$\begingroup\$ This seems to have a significant bias toward languages whose standard library contains BMP functions. For example, php.net/manual/en/function.imagecreatefromwbmp.php \$\endgroup\$ – Greg Hewgill Aug 5 '14 at 23:08
  • \$\begingroup\$ Thats true but allowing external libraries would bring its own set of potential issues and introduce too much scope for loopholes \$\endgroup\$ – rdans Aug 5 '14 at 23:39
  • \$\begingroup\$ Can we at least choose the image format? \$\endgroup\$ – Doorknob Aug 5 '14 at 23:52
  • \$\begingroup\$ @doorknob i updated the question to allow jpeg and png. If anyone wants to use another format, please request it in the comments. \$\endgroup\$ – rdans Aug 6 '14 at 0:10
  • 1
    \$\begingroup\$ PNM? That allows languages with no standard library support for images to participate. \$\endgroup\$ – Peter Taylor Aug 6 '14 at 10:08

12 Answers 12

4
\$\begingroup\$

Parsing the raw bytestream: Haskell (319 = 332 - 13)

Without any imports at all, this program crops P1 PBM images.

g _[]=[]
g z x=(\(a,b)->a:g z b).splitAt z$x
q=map
f=filter
main=do c<-readFile"in.pbm";let(r:p)=tail.f((/='#').head).lines$c;(w:h:_)=(q read).words$r;m=min w h;t s=take m.drop((s-m)`div`2);k d=[[m,m]]++(q(t w)(t h d))in writeFile"out.pbm".("P1\n"++).unlines.q(unwords.(q show)).k.(q((q read).g 1)).take h.g w.f(`elem`"01").concat$p

Special case: helpful formatting (214 = 227 - 13)

My first attempt at PBM (P1) was based on the example at Wikipedia and assumed spaces separated the pixels, and lines in the text correlated with lines in the image. Under those conditions, this shorter version works fine.

main=do c<-readFile"in.pbm";writeFile"out.pbm".("P1\n"++).unlines.q(unwords.(q show)).k.q((q read).words).tail.filter((/='#').head).lines$c
k((w:h:_):d)=let m=min w h;t s=take m.drop((s-m)`div`2)in[[m,m]]++(q(t w)(t h d))
q=map

Bonus feature: strips out comments!

Both of the above work in essentially the same manner as the original below, but with a simpler header, lower bit depth, and no padding, although the topmost program works harder to allow for a range of formatting options.

Also available in a wide range of colours: Haskell (545 = 558 - 13)

No pre-existing image-handling functions were harmed in the making of this program.

Uncompressed 24-bit RGB bitmaps have a fairly straight-forward header system but are complicated by the mod-4 padding for each row's byte array. This code grabs the bitmap offset, width, and height from the input header, calculates the row padding, and extracts the bitmap data into a list of lists (rows of columns, bottom-left corner first).

Cropping is as simple as dropping half the excess and then taking the desired length from each list. The new file size is calculated as 3*(padded width)*height + 54 bytes (for the minimal required headers). The new header is constructed and the cropped image data appended. It's all mapped to characters and written back out to disk.

Input: in.bmp (6 characters removed from total)
Output: out.bmp (7 characters removed from total)

I think there's still some redundancy still in there, but my brain hurts to look at it this way.

import System.IO
import Data.Char
import Data.List.Split
main=do k"in.bmp"ReadMode(\h->do i h u;c<-hGetContents h;let d=l(ord)c;w=s 18d;h=s 22d;p=o w;m=min w h;q=o m;j a b=y a(r b m)in k"out.bmp"WriteMode(\g->do i g u;hPutStr g$"BM"++l chr((z e[m*(3*m+q)+54,0,54,40,m,m,1572865,0,0,1,1,0,0])++((z((++(f q$cycle[0])).y(3*m)(3*r w m)).j m h$chunksOf(3*w+p)$n(s 10d)d)))))
i=hSetBinaryMode
k=withFile
z=concatMap
l=map
v=div
f=take
n=drop
u=True
y t d=f t.n d
s d=sum.zipWith(*)(l(256^)[0..3]).y 4d
o=(`mod`4)
r a b=v(a-b)2
e x=l((`mod`256).(x`v`).(256^))[0..3]

For comparison, an ungolfed version is over 8 times as long, at 4582 (4595 - 13) bytes:

import System.IO
import Data.Char
import Data.List.Split


main = readImage cropImageToSquare


cropImageToSquare fileHandle = do

  hSetBinaryMode fileHandle True
  fileContents <- hGetContents fileHandle

  let
    inImageData         = asBytes fileContents

    inImageBitmapOffset = getIntFromWordAt 10 inImageData
    inImageWidth        = getIntFromWordAt 18 inImageData
    inImageHeight       = getIntFromWordAt 22 inImageData

    inImageBitmapData   = drop inImageBitmapOffset inImageData
    inImageBitmap       = chunksOf (paddedBytesForWidth inImageWidth) inImageBitmapData

    outImageSideLength  = min inImageWidth inImageHeight

    outFileHeader       = fileHeader  outImageSideLength outImageSideLength
    outImageHeader      = imageHeader outImageSideLength outImageSideLength
    outHeaders          = outFileHeader ++ outImageHeader

    outImageBitmap      = crop outImageSideLength outImageSideLength inImageWidth inImageBitmap

    in
      writeImage "out.bmp" outHeaders outImageBitmap


--- Read Image ---------------------------------------------------------

readImage = withFile "in.bmp" ReadMode

--------------------------------------------------------- Read Image ---

--- Write Image --------------------------------------------------------

writeImage filename header imageBitmap = do
  withFile filename WriteMode write'
    where
      write' fileHandle = do
        hSetBinaryMode fileHandle True
        hPutStr fileHandle . ("BM"++)
                           . map chr $ (concatMap wordFromInt header)
                                     ++ (concat imageBitmap)

-------------------------------------------------------- Write Image ---

--- Character-Integer Conversion ---------------------------------------

asBytes = map ord

getWordAt offset = take 4 . drop offset

getIntFromWordAt offset = intFromWord . getWordAt offset

intFromWord     = sum . zipWith (*) (map (256^) [0..3])

wordFromInt int = map((`mod` 256) . (int `div`) . (256^)) [0..3]

--------------------------------------- Character-Integer Conversion ---

--- Headers ------------------------------------------------------------

fileHeader width height = [ fileSize
                          , reserved
                          , bitmapOffset
                          ]
  where
    fileSize     = imageFileSize width height
    reserved     = 0
    bitmapOffset = 54

imageFileSize width height = 54 + height * (paddedBytesForWidth width)
paddedBytesForWidth width  = 3 * width + rowPadding width
rowPadding = (`mod` 4)


imageHeader width height = [ imageHeaderSize
                           , width
                           , height
                           , planesAndBits
                           , compression
                           , bitmapSize
                           , horizontalImageResolution
                           , verticalImageResolution
                           , paletteSize
                           , coloursUsed
                           ]
  where
    imageHeaderSize  = 40
    planesAndBits    = int32FromInt16s colourPlanes bitDepth
    colourPlanes     = 1
    bitDepth         = 24
    compression      = 0
    bitmapSize       = 0
    horizontalImageResolution = pixelsPerMetreFromDPI 72
    verticalImageResolution   = pixelsPerMetreFromDPI 72
    paletteSize      = 0
    coloursUsed      = 0

pixelsPerMetreFromDPI = round . (* (100/2.54))

int32FromInt16s lowBytes highBytes = lowBytes + shift 2 highBytes
shift bytesUp number  = (256 ^ bytesUp) * number

------------------------------------------------------------ Headers ---

--- Crop Image ---------------------------------------------------------

crop toHeight toWidth fromWidth = cropWidth toWidth fromWidth
                                . cropHeight toHeight

cropHeight toHeight image = take toHeight
                          . drop ( halfExtra (length image) toHeight)
                          $ image

cropWidth toWidth fromWidth image = map ( padRow toWidth
                                        . take (3 * toWidth)
                                        . drop (3 * (halfExtra fromWidth toWidth))
                                        )
                                      $ image

padRow toWidth xs = take (paddedBytesForWidth toWidth)
                      $ xs ++ (repeat 0)

halfExtra fromLength toLength = (fromLength - toLength) `div` 2

--------------------------------------------------------- Crop Image ---
\$\endgroup\$
  • \$\begingroup\$ Very nice answer. I'm having a little trouble getting it to work with my test images. see dl.dropboxusercontent.com/u/141246873/crop/bg1ascii.pbm and dl.dropboxusercontent.com/u/141246873/crop/bg1raw.pbm These images are pbm versions of the example image in the question. They were generated using GIMP. One is ascii and the other is raw. Please could you try it with either one of the files. Thanks. \$\endgroup\$ – rdans Aug 6 '14 at 22:00
  • \$\begingroup\$ On the first version, some characters can be saved by shortcutting map to an infix function (like (#)=map) instead of q. Maybe on the second version too, i haven't checked. \$\endgroup\$ – proud haskeller Aug 7 '14 at 8:41
  • \$\begingroup\$ P4 PBMs are out, sorry. As for the P1 image, I followed the Wikipedia example too closely and assumed spaces between pixels and matching line lengths. The new (longer) program I just added handles bg1ascii.pbm, though, and still manages it without importing functionality. \$\endgroup\$ – comperendinous Aug 7 '14 at 11:38
  • \$\begingroup\$ Tested and its working well with the GIMP generated images \$\endgroup\$ – rdans Aug 7 '14 at 18:22
3
\$\begingroup\$

Mathematica 52

Spaces not needed:

ImageCrop[#, {#, #} &@Min@ImageDimensions@#] &@Import@"c:\\test.png"
\$\endgroup\$
  • 1
    \$\begingroup\$ Sorry this is one of the standard loopholes: meta.codegolf.stackexchange.com/a/1078/21713 \$\endgroup\$ – rdans Aug 8 '14 at 21:23
  • 3
    \$\begingroup\$ @Ryan Sorry, but not this time. The OP (you) wrote "The standard libraries of your programming language may be used. Other libraries are disallowed.".I'm using only the standard Mathematica statements - NO LIBRARIES AT ALL \$\endgroup\$ – Dr. belisarius Aug 8 '14 at 21:36
3
\$\begingroup\$

Rebol, 165  92 (105 - 13)

i: load %in.bmp 
s: i/size
m: min s/1 s/2
c: to-pair reduce[m m]save %out.bmp copy/part skip i s - c / 2 c

At its simplest if you have a image file (in.bmp) of 300x200 then it can be cropped and saved to file (out.bmp) like so:

save %out.bmp copy/part skip load %in.bmp 50x0 200x200

Rebol comes with a spatial coordinates datatype called Pair!  Here are some examples of this datatype (using Rebol console):

>> type? 300x200
== pair!

>> first 300x200
== 300.0

>> second 300x200
== 200.0

>> 300x200 - 200x200
== 100x0

>> 300x200 - 200x200 / 2
== 50x0

The last example shows how the skip coordinates were worked out for balanced cropping.


NB. This solution was tested using latest version of Rebol 3 (see http://rebolsource.net/) on OS X. BMP is currently supported on all platforms. PNG, Jpeg & other formats are only partially implemented (across platforms) at this time.

\$\endgroup\$
2
\$\begingroup\$

C# - 250 271 265 229 (incl. 26 for file names)

Forgot about the import for it to work.

using System.Drawing;
class P{static void Main(string[] a){var b=new Bitmap(@"C:\Dev\q.jpg",true);int h=b.Height,w=b.Width,s=h<w?h:w,d=(h-w)/2;b.Clone(new Rectangle(d>0?0:d*-1,d>0?d:0,s,s),b.PixelFormat).Save(@"C:\Dev\q2.jpg");}}

De-golfed (or what you call it).

using System.Drawing;
class Program
{
static void Main(string[] a)
{
var b = new Bitmap(@"C:\Dev\q.jpg",true);
int h=b.Heightw=b.Width,s=h<w?h:w,d=(h-w)/2;
b.Clone(new Rectangle(d>0?0:d*-1,d>0?d:0,s,s),b.PixelFormat).Save(@"C:\Dev\q2.jpg");
}
}

Thanks (again) to w0lf for pointing out different declaration style.

\$\endgroup\$
  • \$\begingroup\$ this can be reduced significantly by erasing extra whitespace and using the int x=..., y=... declaration style: pastie.org/9454985 \$\endgroup\$ – Cristian Lupascu Aug 8 '14 at 6:34
  • \$\begingroup\$ also, you can drop the spaces around equal signs and most line breaks. See the link in my previous comment. It's 232 chars. \$\endgroup\$ – Cristian Lupascu Aug 8 '14 at 8:32
  • \$\begingroup\$ Thanks! I guess it is better to actually look at the link :) \$\endgroup\$ – Sander Aug 8 '14 at 8:44
  • \$\begingroup\$ Yes. Well, at both links because I just realized thsi can be shortened even more. :) \$\endgroup\$ – Cristian Lupascu Aug 8 '14 at 9:14
  • \$\begingroup\$ Well, according to the rules the image-reference don't count, but yeah, nice! \$\endgroup\$ – Sander Aug 8 '14 at 9:17
2
\$\begingroup\$

Java, 373 348 342 336 322

import java.awt.image.*;import java.io.*;import javax.imageio.*;class M{public static void main(String[]a)throws Exception{BufferedImage i=ImageIO.read(new File(a[0]));int[]d={i.getWidth(),i.getHeight()},o={0,0};int s=d[0]>d[1]?1:0;o[1-s]=(d[1-s]-d[s])/2;ImageIO.write(i.getSubimage(o[0],o[1],d[s],d[s]),"bmp",new File("out.bmp"));}}

Ungolfed:

import java.awt.image.*;
import java.io.*;

import javax.imageio.*;

class M{
    public static void main(String[] args) throws Exception{
        // read file as argument, instead of args[0] the path to the file could hardcoded.
        // Since the path to the input file don't count towards the character count, I'm not counting args[0]
        BufferedImage i = ImageIO.read(new File(args[0]));

        int[] dimension = { i.getWidth(), i.getHeight() }, origin = { 0, 0 };

        int smaller = dimension[0] > dimension[1] ? 1 : 0;

        // 1-smaller is the index of the bigger dimension
        origin[1-smaller] = (dimension[1-smaller] - dimension[smaller]) / 2;

        // again, path to output file don't count
        ImageIO.write(i.getSubimage(origin[0], origin[1], dimension[smaller], dimension[smaller]), "bmp", new File("out.bmp"));
    }
}

Usage: java M image.type

Should be able to read jpg, png, bmp, wbmp and gif. Writes a bmp called "o".

Who said you couldn't use Java for golf?

Edit: Just realised that file paths don't count towards the character count.

\$\endgroup\$
  • \$\begingroup\$ You can use * for imports.. Like import java.io.* etc ;) \$\endgroup\$ – Jaa-c Aug 6 '14 at 17:50
  • \$\begingroup\$ I'm so used to eclipse handling the imports that I didn't even think of that. There was other stuff that could be removed, too. E: Oops, forgot an import. \$\endgroup\$ – IchBinKeinBaum Aug 6 '14 at 18:02
  • \$\begingroup\$ You can cut 9 by removing public from the class and the two spaces in String[] a) throws. You should add a golfed (no whitespace) version so it's easier for others to count/verify, though. \$\endgroup\$ – Geobits Aug 6 '14 at 18:55
  • \$\begingroup\$ @Geobits done. Also apparently I counted too many characters. Should be fixed now. \$\endgroup\$ – IchBinKeinBaum Aug 6 '14 at 19:29
  • \$\begingroup\$ Works nicely for portrait/landscape images but doesn't seem to produce any output for square images? \$\endgroup\$ – rdans Aug 6 '14 at 20:35
2
\$\begingroup\$

Bash, 115

with ImageMagic, hard to say if you can count it as a standard lib :)

#!/bin/bash
convert $1 -set option:size '%[fx:min(h,w)]x%[fx:min(h,w)]' xc:red +swap -gravity center -composite _$1
\$\endgroup\$
  • \$\begingroup\$ I'm not counting it as a standard lib sorry :) \$\endgroup\$ – rdans Aug 6 '14 at 20:21
1
\$\begingroup\$

Clojure, 320 (333 - 13 for filenames)

Deciding to sidestep types, this P1 (ASCII) PBM cropper is written in Clojure.

With function names like interpose, read-string, and clojure.string/replace, it's not exactly golf friendly, and I'm not sufficiently experienced to know if I can compile without a namespace declaration. (Leiningen puts it in automatically, and Leiningen is nice to me, so I'm going to leave it there, though I did eject .core.) On the plus side, however, file access and string concatenation are short.

(ns a(:gen-class))(defn -main[](let[[[_ r p]](re-seq
#"(^\s*\d+\s+\d+)([\s\S]*$)"(clojure.string/replace(slurp"in.pbm")#"(P1)|(#.*\n)"""))[w
h](map read-string(re-seq #"\d+"r))c(min w h)](spit"out.pbm"(apply
str"P1\n"c" "c"\n"(flatten(interpose"\n"(take
c(partition c w(drop(+(*(quot(- h c)2)w)(quot(- w c)2))(re-seq #"\S"p))))))))))

The function at the heart of the program is ungolfed below.

(defn crop-pbm [in out]
  (let [         filestream (slurp in)
                 image-data (clojure.string/replace (str filestream "\n") #"(P1)|(#.*\n)" "")
    [[_ resolution pixels]] (re-seq #"(^\s*\d+\s+\d+)([\s\S]*$)" image-data)
                     pixels (re-seq #"\S" pixels)
             [width height] (map read-string (re-seq #"\d+" resolution))
                       crop (min width height)
                 drop-width (- width  crop)
                drop-height (- height crop)
         initial-drop-width (quot drop-width  2)
        initial-drop-height (quot drop-height 2)
               initial-drop (+ (* initial-drop-height width) initial-drop-width)
             cropped-pixels (take crop
                                  (partition crop width
                                             (drop initial-drop pixels)))
              cropped-image (apply str "P1\n" crop " " crop "\n"
                                       (flatten (interpose "\n" cropped-pixels)))]
      (spit out cropped-image)))

Rather than constructing a two-dimensional array to represent the image, this program calculates the number of pixels to the new top-left corner and drops them, then jumps original-width pixels at a time, taking cropped-side-length pixels from as many steps.

The regular expression function re-seq does much of the heavy lifting, stripping out whitespace and forming arrays simultaneously, which leads me to suspect that Perl could follow this approach and yield a shorter program.

\$\endgroup\$
1
\$\begingroup\$

Perl, 276 287 (289 300 - 13)

Regex to the Rescue!

Who needs dedicated image functions when you have regular expressions? (And 1-bit images to crop.)

local$/;open I,"in.pbm";$_=<I>;s/(P1)|(#.*\n)//g;/^\s*(\d+)\s+(\d+)([\s\S]*)$/;($w,$h,$_)=($1,$2,$3);$c=$w<$h?$w:$h;s/\D//g;$d=$w-$c;while($z++<$w*int(($h-$c)/2)+int($d/2)){s/^\d//};while($i++<$c){/^(\d{$c})(\d{$d})(.*)/;$p.=$1."\n";$_=$3."0"x$w}open O,'>',"out.pbm";print O"P1\n$c $c\n$p"

That's the golfed version of:

use strict;
use warnings;

# Read in the image file
local $/ = undef;

open IMAGEIN, '<', "in.pbm" or die "Can't read 'in.pbm': $!";
my $image_data = <IMAGEIN>;
close IMAGEIN;

# Remove the header and any comments
$_ = $image_data;
s/(P1)|(#.*\n)//g;

# Divide into width, height, and pixels
/^\s*(\d+)\s+(\d+)([\s\S]*)$/;
(my $width, my $height, $_) = ($1, $2, $3);

# Remove anything that isn't a number from the pixel data
s/\D//g;
my $pixels = $_;

# Determine the new dimensions
my $crop = $width < $height ? $width : $height;

# Calculate total to remove along each axis
my $drop_width  = $width  - $crop;
my $drop_height = $height - $crop;

# Calculate how much to remove from the first side along each axis
my $initial_drop_width  = int($drop_width  / 2);
my $initial_drop_height = int($drop_height / 2);

# Calculate total pixels to the new top-left corner
my $initial_drop = $width * $initial_drop_height + $initial_drop_width;

# Remove the pixels preceding the corner
$_ = $pixels;
while (32760 < $initial_drop)  # Stay under regex limit
{
    s/^\d{32760}//;
    $initial_drop -= 32760;
}
s/^\d{$initial_drop}//;

# Take *crop* rows of *crop* pixels
$pixels = "";
for (my $i=0; $i<$crop; $i++)
{
    /^(\d{$crop})(\d{$drop_width})(.*)/;
    $pixels .= $1 . "\n";
    $_ = $3 . "0"x$width;  # Add some 0s to ensure final match
}

# Construct the new, cropped image
my $cropped_image = "P1\n$crop $crop\n$pixels";

# Write out the image file
open IMAGEOUT, '>', "out.pbm" or die "Can't write 'out.pbm': $!";
print IMAGEOUT $cropped_image;
close IMAGEOUT;

There's a limit to the length of a string the substitution functions will handle, which necessitates a bothersome while loop for taller images. Even so, finally under 300!

\$\endgroup\$
1
\$\begingroup\$

C#, 420

If the image is portrait it gets rotated while cropping and then cropped image is rotated to correct orientation.

Edit: now crop centre of the image. Run in terminal as crop.exe a.jpg

class P
{
static void Main(string[] a)
{
if (a.Length > 0)
{
Bitmap b = (Bitmap)Image.FromFile(a[0]);
int w = b.Width;
int h = b.Height;
int d = Math.Abs(w - h) / 2;
if (h > w) { b.RotateFlip(RotateFlipType.Rotate270FlipNone); }
Bitmap c = new Bitmap(h, h);
for (int x = d; x < (h+d); x++)
{
for (int y = 0; y < h; y++)
{
c.SetPixel(x-d, y, b.GetPixel(x, y));
}
}
if (h > w) { c.RotateFlip(RotateFlipType.Rotate90FlipNone); }
c.Save("Q.jpg");
}
}
}
\$\endgroup\$
  • \$\begingroup\$ Unfortunately this throws an exception on an image with portrait orientation. It also outputs the left side of the input image and not the central part. To see what I mean, try it with the example image in the question and compare your result. \$\endgroup\$ – rdans Aug 6 '14 at 19:05
0
\$\begingroup\$

C++/Qt 146

This might technically break rule #2 as Qt is not part of standard C++.

The first argument will be the input filename and the second will be the output filename.

#include<QtGui>
main(int c,char**v){QApplication a(c,v);QPixmap p(v[1]);int w=p.width(),h=p.height(),m=qMin(w,h);p.copy(w-m,h-m,m,m).save(v[2]);}
\$\endgroup\$
  • \$\begingroup\$ This answer is not valid due to the use of a non standard library, but thanks for taking part :) \$\endgroup\$ – rdans Aug 6 '14 at 19:25
0
\$\begingroup\$

Io, 103 bytes (excluding file path)

i := Image open("img.png");e := (i width) min(i height);i crop(((i width)-e)/2,((i height)-e)/2,e,e) save("crop.png")

You can use Io for CG!

\$\endgroup\$
  • \$\begingroup\$ This can be heavily 'golfed' even further \$\endgroup\$ – georgeunix Aug 27 '15 at 6:01
0
\$\begingroup\$

Matlab, 116 (130 with file names)

Pretty straightforward.

I=imread('a.jpg');[m,n]=size(I);z=min(m,n);c=m<n;r=[1,ceil((m-z)/2)]*~c+c*[ceil((n-z)/2),1];imwrite(imcrop(I,[r,z-1,z-1]),'z.jpg')
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.